﻿using System;

using System.Text;
using RoughJs.Error;
using RoughJs.Helper;

namespace RoughJs.Ast
{
    /// <summary>
    /// 2010-01-5 ----- 2010-01-29
    /// 陈曦 1.0
    /// Microsoft Public License (Ms-PL)
    ///This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
    ///1. Definitions
    ///The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
    ///A "contribution" is the original software, or any additions or changes to the software.
    ///A "contributor" is any person that distributes its contribution under this license.
    ///"Licensed patents" are a contributor's patent claims that read directly on its contribution.
    ///2. Grant of Rights
    ///(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
    ///(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
    ///3. Conditions and Limitations
    ///(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
    ///(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
    ///(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
    ///(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
    ///(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 
    /// </summary>
    public class ScriptRuntime
    {
        private static String[] lazilyNames = {
      
        "XML",           "(xml)",
        "XMLList",       "(xml)",
        "Namespace",     "(xml)",
        "QName",         "(xml)",
    };
        public class NoSuchMethodShim : Callable
        {
            public String methodName;
            public Callable noSuchMethodMethod;

            public NoSuchMethodShim(Callable noSuchMethodMethod, String methodName)
            {
                this.noSuchMethodMethod = noSuchMethodMethod;
                this.methodName = methodName;
            }
            /**
             * Perform the call.
             *
             * @param cx the current Context for this thread
             * @param scope the scope to use to resolve properties.
             * @param thisObj the JavaScript <code>this</code> object
             * @param args the array of arguments
             * @return the result of the call
             */
            public Object call(Context cx, Scriptable scope, Scriptable thisObj,
                               Object[] args)
            {
                Object[] nestedArgs = new Object[2];

                nestedArgs[0] = methodName;
                nestedArgs[1] = newArrayLiteral(args, null, cx, scope);
                return noSuchMethodMethod.call(cx, scope, thisObj, nestedArgs);
            }

        }
        public static Scriptable newArrayLiteral(Object[] objects,
                                             int[] skipIndices,
                                             Context cx, Scriptable scope)
        {
            int SKIP_DENSITY = 2;
            int count = objects.Length;
            int skipCount = 0;
            if (skipIndices != null)
            {
                skipCount = skipIndices.Length;
            }
            int length = count + skipCount;
            if (length > 1 && skipCount * SKIP_DENSITY < length)
            {
                // If not too sparse, create whole array for constructor
                Object[] sparse;
                if (skipCount == 0)
                {
                    sparse = objects;
                }
                else
                {
                    sparse = new Object[length];
                    int skip = 0;
                    for (int i = 0, j = 0; i != length; ++i)
                    {
                        if (skip != skipCount && skipIndices[skip] == i)
                        {
                            sparse[i] = ScriptableObject.NOT_FOUND;
                            ++skip;
                            continue;
                        }
                        sparse[i] = objects[j];
                        ++j;
                    }
                }
                return cx.newObject(scope, "Array", sparse);
            }

            Scriptable arrayObj = cx.newObject(scope, "Array",
                                               ScriptRuntime.emptyArgs);
            int skip1 = 0;
            for (int i = 0, j = 0; i != length; ++i)
            {
                if (skip1 != skipCount && skipIndices[skip1] == i)
                {
                    ++skip1;
                    continue;
                }
                ScriptableObject.putProperty(arrayObj, i, objects[j]);
                ++j;
            }
            return arrayObj;
        }


        public static Function getExistingCtor(Context cx, Scriptable scope,
                                        String constructorName)
        {
            Object ctorVal = ScriptableObject.getProperty(scope, constructorName);
            if (ctorVal is Function)
            {
                return (Function)ctorVal;
            }
            if (ctorVal == ScriptableObject.NOT_FOUND)
            {
                throw Context.reportRuntimeError1(
                    "msg.ctor.not.found", constructorName);
            }
            else
            {
                throw Context.reportRuntimeError1(
                    "msg.not.ctor", constructorName);
            }
        }


        /// <summary>
        /// 初始化标准对象
        /// </summary>
        /// <param name="cx">当前上下文</param>
        /// <param name="scope">创建域</param>
        /// <param name="sealed1"></param>
        /// <returns></returns>
        public static ScriptableObject initStandardObjects(Context cx,
                                                       ScriptableObject scope,
                                                       bool sealed1)
        {
            if (scope == null)
            {
                scope = new NativeObject();
            }
            scope.associateValue(LIBRARY_SCOPE_KEY, scope);
            //(new ClassCache()).associate(scope);

            BaseFunction.init(scope, sealed1);
            NativeObject.init(scope, sealed1);
            Scriptable objectProto = ScriptableObject.getObjectPrototype(scope);


            Scriptable functionProto = ScriptableObject.getFunctionPrototype(scope);
            functionProto.setPrototype(objectProto);


            if (scope.getPrototype() == null)
                scope.setPrototype(objectProto);

            NativeError.init(scope, sealed1);
            NativeGlobal.init(cx, scope, sealed1);

            NativeArray.init(scope, sealed1);
            if (cx.getOptimizationLevel() > 0)
            {

                NativeArray.setMaximumInitialCapacity(200000);
            }
            NativeString.init(scope, sealed1);
            NativeBoolean.init(scope, sealed1);
            NativeNumber.init(scope, sealed1);
            NativeScript.init(scope, sealed1);

            NativeIterator.init(scope, sealed1);

            bool withXml = cx.hasFeature(Context.FEATURE_E4X);
            for (int i = 0; i != lazilyNames.Length; i += 2)
            {
                String topProperty = lazilyNames[i];
                String className = lazilyNames[i + 1];
                if (!withXml && className == "(xml)")
                {
                    continue;
                }
                else if (withXml && className == "(xml)")
                {
                    //className = cx.getE4xImplementationFactory().
                    //              getImplementationClassName();
                    className = "RoughJs.E4X.XMLLibImpl";
                }
                new LazilyLoadedCtor(scope, topProperty, className, sealed1);
            }

            return scope;

        }

        //public static EcmaError typeError1(String messageId, String arg1)
        //{
        //    String msg = getMessage1(messageId, arg1);
        //    return typeError(msg);
        //}
        public static Type ScriptableClass = typeof(Scriptable);

        public static String defaultObjectToSource(Context cx, Scriptable scope,
                                         Scriptable thisObj, Object[] args)
        {
            bool toplevel, iterating;
            if (cx.iterating == null)
            {
                toplevel = true;
                iterating = false;
                cx.iterating = new ObjToIntMap(31);
            }
            else
            {
                toplevel = false;
                iterating = cx.iterating.has(thisObj);
            }

            StringBuilder result = new StringBuilder(128);
            if (toplevel)
            {
                result.Append("(");
            }
            result.Append('{');

            // Make sure cx.iterating is set to null when done
            // so we don't leak memory
            try
            {
                if (!iterating)
                {
                    cx.iterating.intern(thisObj); // stop recursion.
                    Object[] ids = thisObj.getIds();
                    for (int i = 0; i < ids.Length; i++)
                    {
                        Object id = ids[i];
                        Object value;
                        if (id is int)
                        {
                            int intId = Convert.ToInt32(id);
                            value = thisObj.get(intId, thisObj);
                            if (value == ScriptableObject.NOT_FOUND)
                                continue;   // a property has been removed
                            if (i > 0)
                                result.Append(", ");
                            result.Append(intId);
                        }
                        else
                        {
                            String strId = (String)id;
                            value = thisObj.get(strId, thisObj);
                            if (value == ScriptableObject.NOT_FOUND)
                                continue;   // a property has been removed
                            if (i > 0)
                                result.Append(", ");
                            if (ScriptRuntime.isValidIdentifierName(strId))
                            {
                                result.Append(strId);
                            }
                            else
                            {
                                result.Append('\'');
                                result.Append(
                                    ScriptRuntime.escapeString(strId, '\''));
                                result.Append('\'');
                            }
                        }
                        result.Append(':');
                        result.Append(ScriptRuntime.uneval(cx, scope, value));
                    }
                }
            }
            finally
            {
                if (toplevel)
                {
                    cx.iterating = null;
                }
            }

            result.Append('}');
            if (toplevel)
            {
                result.Append(')');
            }
            return result.ToString();
        }

        public static bool isValidIdentifierName(String s)
        {

            int L = s.Length;
            if (L == 0)
                return false;
            if (!Character.isJavaIdentifierStart(s.ToCharArray()[0]))
                return false;
            for (int i = 1; i != L; ++i)
            {
                if (!Character.isJavaIdentifierPart(s.ToCharArray()[i]))
                    return false;
            }
            return !TokenStream.isKeyword(s);
        }


        public static String getMessage1(String messageId, Object arg1)
        {
            Object[] arguments = { arg1 };
            return getMessage(messageId, arguments);
        }
        public static MessageProvider messageProvider = new DefaultMessageProvider();
        public static String getMessage(String messageId, Object[] arguments)
        {
            return messageProvider.getMessage(messageId, arguments);
        }
        // It is public so NativeRegExp can access it.
        public static bool isJSLineTerminator(int c)
        {
            // Optimization for faster check for eol character:
            // they do not have 0xDFD0 bits set
            if ((c & 0xDFD0) != 0)
            {
                return false;
            }
            return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
        }

        /// <summary>
        /// 将数字转换为字符串
        /// </summary>
        /// <param name="d">值</param>
        /// <param name="bas">进制</param>
        /// <returns></returns>
        public static String numberToString(double d, int bas)
        {
            if (Double.IsNaN(d))
                return "NaN";
            if (d == Double.PositiveInfinity)
                return "Infinity";
            if (d == Double.NegativeInfinity)
                return "-Infinity";
            if (d == 0.0)
                return "0";

            if ((bas < 2) || (bas > 36))
            {
                throw Context.reportRuntimeError1(
                    "msg.bad.radix", bas.ToString());
            }

            if (bas != 10)
            {
                return DToA.JS_dtobasestr(bas, d);
            }
            else
            {
                StringBuilder result = new StringBuilder();
                DToA.JS_dtostr(result, DToA.DTOSTR_STANDARD, 0, d);
                return result.ToString();
            }

        }


        public static void setFunctionProtoAndParent(BaseFunction fn,
                                                Scriptable scope)
        {
            fn.setParentScope(scope);
            fn.setPrototype(ScriptableObject.getFunctionPrototype(scope));
        }

        public static bool isGeneratedScript(String sourceUrl)
        {
            // ALERT: this may clash with a valid URL containing (eval) or
            // (Function)
            return sourceUrl.IndexOf("(eval)") >= 0
                   || sourceUrl.IndexOf("(Function)") >= 0;
        }


        public static bool isSpecialProperty(String s)
        {
            return s == "__proto__" || s == "__parent__";
        }
        /**
 * If s represents index, then return index value wrapped as Integer
 * and othewise return s.
 */
        public static Object getIndexObject(String s)
        {
            long indexTest = indexFromString(s);
            if (indexTest >= 0)
            {
                return (int)indexTest;
            }
            return s;
        }

        /**
    * Return -1L if str is not an index or the index value as lower 32
    * bits of the result.
    */
        private static long indexFromString(String str)
        {
            // The length of the decimal string representation of
            //  Integer.MAX_VALUE, 2147483647
            int MAX_VALUE_LENGTH = 10;

            int len = str.Length;
            if (len > 0)
            {
                int i = 0;
                bool negate = false;
                int c = str.ToCharArray()[0];
                if (c == '-')
                {
                    if (len > 1)
                    {
                        c = str.ToCharArray()[1];
                        i = 1;
                        negate = true;
                    }
                }
                c -= '0';
                if (0 <= c && c <= 9
                    && len <= (negate ? MAX_VALUE_LENGTH + 1 : MAX_VALUE_LENGTH))
                {
                    // Use negative numbers to accumulate index to handle
                    // Integer.MIN_VALUE that is greater by 1 in absolute value
                    // then Integer.MAX_VALUE
                    int index = -c;
                    int oldIndex = 0;
                    i++;
                    if (index != 0)
                    {
                        // Note that 00, 01, 000 etc. are not indexes
                        while (i != len && 0 <= (c = str.ToCharArray()[i] - '0') && c <= 9)
                        {
                            oldIndex = index;
                            index = 10 * index - c;
                            i++;
                        }
                    }
                    // Make sure all characters were consumed and that it couldn't
                    // have overflowed.
                    if (i == len &&
                        (oldIndex > (int.MinValue / 10) ||
                         (oldIndex == (int.MinValue / 10) &&
                          c <= (negate ? -(int.MinValue % 10)
                                       : (int.MaxValue % 10)))))
                    {
                        return 0xFFFFFFFFL & (negate ? index : -index);
                    }
                }
            }
            return -1L;
        }

        /**
  * If d is exact int value, return its value wrapped as Integer
  * and othewise return d converted to String.
  */
        public static Object getIndexObject(double d)
        {
            int i = (int)d;
            if (i == d)
            {
                return i;
            }
            return toString(d);
        }
        /**
     * Optimized version of toString(Object) for numbers.
     */
        public static String toString(double val)
        {
            return numberToString(val, 10);
        }
        /// <summary>
        /// 转换为String
        /// 
        /// 参看 ECMA 9.8.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static String toString(Object val)
        {
            for (; ; )
            {
                if (val == null)
                {
                    return "null";
                }
                if (val == Undefined.instance)
                {
                    return "undefined";
                }
                try
                {
                    if (val is Scriptable)
                    {
                        val = ((Scriptable)val).getDefaultValue(StringClass);
                        if (val is Scriptable)
                        {
                            throw errorWithClassName("msg.primitive.expected", val);
                        }

                        return val.ToString();
                    }

                    return Convert.ToString(val);
                }
                catch
                {
                    return val.ToString();
                }

            }
        }

        public static String toString(Object[] args, int index)
        {
            return (index < args.Length) ? toString(args[index]) : "undefined";
        }
        /// <summary>
        /// 转换为bool
        /// See ECMA 9.2.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static bool toBoolean(Object val)
        {
            for (; ; )
            {

                if (val is Scriptable)
                {
                    if (val is ScriptableObject &&
                        ((ScriptableObject)val).avoidObjectDetection())
                    {
                        return false;
                    }
                    if (Context.getContext().isVersionECMA1())
                    {
                        // pure ECMA
                        return true;
                    }
                    // ECMA 扩展
                    val = ((Scriptable)val).getDefaultValue(BooleanClass);
                    if (val is Scriptable)
                        throw errorWithClassName("msg.primitive.expected", val);
                    continue;
                }

                try
                {
                    return Convert.ToBoolean(val);
                }
                catch
                {
                    warnAboutNonJSObject(val);
                    return true;
                }


            }
        }

        /// <summary>
        /// 转换为Int32
        /// 参看 ECMA 9.5.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static int toInt32(double d)
        {
            int id = (int)d;
            if (id == d)
            {
                // This covers -0.0 as well
                return id;
            }

            if (Double.IsNaN(d)
                || Double.IsInfinity(d))
            {
                return 0;
            }

            d = (d >= 0) ? Math.Floor(d) : Math.Ceiling(d);

            //double two32 = 4294967296.0;
            //d = Math.IEEERemainder(d, two32);
            // (double)(long)d == d should hold here

            //long l = (long)d;
            // returning (int)d does not work as d can be outside int range
            // but the result must always be 32 lower bits of l
            //return (int)l;
            return Convert.ToInt32(d);
        }
        /// <summary>
        /// 转换为Int32
        /// 参看 ECMA 9.5.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static int toInt32(Object val)
        {
            // short circuit for common integer values
            if (val is Int32 || val is Int16)
                return Convert.ToInt32(val);

            return toInt32(toNumber(val));
        }

        /// <summary>
        /// 转换为Int32
        /// 参看 ECMA 9.5.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static int toInt32(Object[] args, int index)
        {
            return (index < args.Length) ? toInt32(args[index]) : 0;
        }

        /// <summary>
        /// 参看 ECMA 9.7.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static char toUint16(Object val)
        {
            double d = toNumber(val);

            int i = (int)d;
            if (i == d)
            {
                return (char)i;
            }

            if (Double.IsInfinity(d))
            {
                return Convert.ToChar(0);
            }

            d = (d >= 0) ? Math.Floor(d) : Math.Ceiling(d);

            int int16 = 0x10000;
            i = (int)Math.IEEERemainder(d, int16);

            return (char)i;
        }

        /// <summary>
        /// 转换为32位 无符号整数
        /// ECMA 9.6.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static long toUint32(double d)
        {
            long l = (long)d;
            if (l == d)
            {
                // This covers -0.0 as well
                return l & 0xffffffffL;
            }

            if (Double.IsInfinity(d))
            {
                return 0;
            }

            d = (d >= 0) ? Math.Floor(d) : Math.Ceiling(d);

            // 0x100000000 gives me a numeric overflow...
            double two32 = 4294967296.0;
            l = (long)Math.IEEERemainder(d, two32);

            return l & 0xffffffffL;
        }

        /// <summary>
        /// 转换为32位符号整数
        /// ECMA 9.6.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static long toUint32(Object val)
        {
            return toUint32(toNumber(val));
        }

        /// <summary>
        /// 转换为整形
        /// 参看ECMA 9.4.
        /// </summary>
        /// <param name="d"></param>
        /// <returns></returns>
        public static double toInteger(double d)
        {
            return toInt32(d);
        }
        /// <summary>
        /// 转换为整形
        /// 参看ECMA 9.4.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static double toInteger(Object val)
        {
            return toInteger(toNumber(val));
        }
        /// <summary>
        /// 转换为整形
        /// 参看ECMA 9.4.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static double toInteger(Object[] args, int index)
        {
            return toInt32(args, 0);
        }


        public static Scriptable toIterator(Context cx, Scriptable scope,
                                        Scriptable obj, bool keyOnly)
        {
            if (ScriptableObject.hasProperty(obj,
                NativeIterator.ITERATOR_PROPERTY_NAME))
            {
                Object v = ScriptableObject.getProperty(obj,
                    NativeIterator.ITERATOR_PROPERTY_NAME);
                if (!(v is Callable))
                {
                    throw typeError0("msg.invalid.iterator");
                }
                Callable f = (Callable)v;
                Object[] args = new Object[] { keyOnly ? true
                                                   : false };
                v = f.call(cx, scope, obj, args);
                if (!(v is Scriptable))
                {
                    throw typeError0("msg.iterator.primitive");
                }
                return (Scriptable)v;
            }
            return null;
        }

        /// <summary>
        /// 把值转换为数字
        ///  参看 ECMA 9.3.
        /// </summary>
        /// <param name="val"></param>
        /// <returns></returns>
        public static double toNumber(Object val)
        {
            for (; ; )
            {

                if (val == null)
                    return +0.0;
                if (val == Undefined.instance)
                    return Double.NaN;
                //if (val is Number)
                //    return ((Number) val).doubleValue();
                if (val is String)
                    return toNumber((String)val);
                //if (val is bool )
                //    return ((Boolean) val).booleanValue() ? 1 : +0.0;
                try
                {
                    //double result = 0;
                    return Convert.ToDouble(val);
                }
                catch (InvalidCastException ex)
                {
                    if (val is Scriptable)
                    {
                        val = ((Scriptable)val).getDefaultValue(NumberClass);
                        if (val is Scriptable)
                            throw errorWithClassName("msg.primitive.expected", val);
                        continue;
                    }
                    warnAboutNonJSObject(val);
                    return Double.NaN;
                }
            }
        }

        /// <summary>
        /// 把值转换为数字
        /// 参看 ECMA 9.3.
        /// </summary>
        /// <param name="args"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static double toNumber(Object[] args, int index)
        {
            return (index < args.Length) ? toNumber(args[index]) : Double.NaN;
        }
        private static bool MSJVM_BUG_WORKAROUNDS = true;
        public static double toNumber(String s)
        {
            int len = s.Length;
            int start = 0;
            char startChar;
            for (; ; )
            {
                if (start == len)
                {
                    // Empty or contains only whitespace
                    return +0.0;
                }
                startChar = s.ToCharArray()[start];
                if (!Char.IsWhiteSpace(startChar))
                    break;
                start++;
            }

            if (startChar == '0')
            {
                if (start + 2 < len)
                {
                    int c1 = s.ToCharArray()[start + 1];
                    if (c1 == 'x' || c1 == 'X')
                    {
                        // A hexadecimal number
                        return stringToNumber(s, start + 2, 16);
                    }
                }
            }
            else if (startChar == '+' || startChar == '-')
            {
                if (start + 3 < len && s.ToCharArray()[start + 1] == '0')
                {
                    int c2 = s.ToCharArray()[start + 2];
                    if (c2 == 'x' || c2 == 'X')
                    {
                        // A hexadecimal number with sign
                        double val = stringToNumber(s, start + 3, 16);
                        return startChar == '-' ? -val : val;
                    }
                }
            }

            int end = len - 1;
            char endChar;
            while (Char.IsWhiteSpace(endChar = s.ToCharArray()[end]))
                end--;
            if (endChar == 'y')
            {
                // check for "Infinity"
                if (startChar == '+' || startChar == '-')
                    start++;
                if (start + 7 == end && s.Substring(start) == "Infinity")
                    return startChar == '-'
                        ? Double.NegativeInfinity
                        : Double.PositiveInfinity;
                return Double.NaN;
            }
            // A non-hexadecimal, non-infinity number:
            // just try a normal floating point conversion
            String sub = s.Substring(start, end + 1);
            if (MSJVM_BUG_WORKAROUNDS)
            {
                // The MS JVM will accept non-conformant strings
                // rather than throwing a NumberFormatException
                // as it should.
                for (int i = sub.Length - 1; i >= 0; i--)
                {
                    char c = sub.ToCharArray()[i];
                    if (('0' <= c && c <= '9') || c == '.' ||
                        c == 'e' || c == 'E' ||
                        c == '+' || c == '-')
                        continue;
                    return Double.NaN;
                }
            }
            try
            {
                return Convert.ToDouble(sub);
            }
            catch (FormatException ex)
            {
                return Double.NaN;
            }
        }


        /// <summary>
        /// 转换为对象
        /// </summary>
        /// <param name="cx"></param>
        /// <param name="scope"></param>
        /// <param name="val"></param>
        /// <returns></returns>
        public static Scriptable toObject(Context cx, Scriptable scope, Object val)
        {
            if (val is Scriptable)
            {
                return (Scriptable)val;
            }
            if (val == null)
            {
                throw typeError0("msg.null.to.object");
            }
            if (val == Undefined.instance)
            {
                throw typeError0("msg.undef.to.object");
            }
            String className = val is String ? "String" :
                                val is char ? "String" :
                                val is decimal ? "Number" :
                               val is double ? "Number" :
                               val is float ? "Number" :
                               val is long ? "Number" :
                               val is int || val is Int32 ? "Number" :
                               val is bool ? "Boolean" :
                               null;
            if (className != null)
            {
                Object[] args = { val };
                scope = ScriptableObject.getTopLevelScope(scope);
                return newObject(cx, scope, className, args);
            }

            // Extension: Wrap as a LiveConnect object.
            Object wrapped = cx.getWrapFactory().wrap(cx, scope, val, null);
            if (wrapped is Scriptable)
                return (Scriptable)wrapped;
            throw errorWithClassName("msg.invalid.type", val);
        }


        public static Scriptable newObject(Context cx, Scriptable scope,
                                       String constructorName, Object[] args)
        {
            scope = ScriptableObject.getTopLevelScope(scope);
            Function ctor = getExistingCtor(cx, scope, constructorName);
            if (args == null) { args = ScriptRuntime.emptyArgs; }
            return ctor.construct(cx, scope, args);
        }

        //static Function getExistingCtor(Context cx, Scriptable scope,
        //                            String constructorName)
        //{
        //    Object ctorVal = ScriptableObject.getProperty(scope, constructorName);
        //    if (ctorVal is Function)
        //    {
        //        return (Function)ctorVal;
        //    }
        //    if (ctorVal == ScriptableObject.NOT_FOUND)
        //    {
        //        throw Context.reportRuntimeError1(
        //            "msg.ctor.not.found", constructorName);
        //    }
        //    else
        //    {
        //        throw Context.reportRuntimeError1(
        //            "msg.not.ctor", constructorName);
        //    }
        //}

        public static Object getTopLevelProp(Scriptable scope, String id)
        {
            scope = ScriptableObject.getTopLevelScope(scope);
            return ScriptableObject.getProperty(scope, id);
        }


        public static String toStringIdOrIndex(Context cx, Object id)
        {
            if (id is String)
            {
                String s;
                if (id is String)
                {
                    s = (String)id;
                }
                else
                {
                    s = toString(id);
                }
                long indexTest = indexFromString(s);
                if (indexTest >= 0)
                {
                    storeIndexResult(cx, (int)indexTest);
                    return null;
                }
                return s;
            }
            else
            {

                double d = Convert.ToDouble(id);
                int index = (int)d;
                if (index == d)
                {
                    storeIndexResult(cx, index);
                    return null;
                }
                return toString(id);


            }
        }


        private static void storeIndexResult(Context cx, int index)
        {
            cx.scratchIndex = index;
        }

        public static int lastIndexResult(Context cx)
        {
            return cx.scratchIndex;
        }

        public static void storeUint32Result(Context cx, long value)
        {
            if (MathKit.foo2(value, 32) != 0)
                throw new ArgumentException();
            cx.scratchUint32 = value;
        }

        public static long lastUint32Result(Context cx)
        {
            long value = cx.scratchUint32;
            if (MathKit.foo2(value, 32) != 0)
                throw new ArgumentException();
            return value;
        }


        public static void setObjectProtoAndParent(ScriptableObject object1,
                                               Scriptable scope)
        {
            // Compared with function it always sets the scope to top scope
            scope = ScriptableObject.getTopLevelScope(scope);
            object1.setParentScope(scope);
            Scriptable proto
                = ScriptableObject.getClassPrototype(scope, object1.getClassName());
            object1.setPrototype(proto);
        }

        /**
     * The is operator.
     *
     * @return a is b
     */
        public static bool instanceOf(Object a, Object b, Context cx)
        {
            // Check RHS is an object
            if (!(b is Scriptable))
            {
                throw typeError0("msg.instanceof.not.object");
            }

            // for primitive values on LHS, return false
            // XXX we may want to change this so that
            // 5 is Number == true
            if (!(a is Scriptable))
                return false;

            return ((Scriptable)b).hasInstance((Scriptable)a);
        }

        /**
    * Equality
    *
    * See ECMA 11.9
    */
        public static bool eq(Object x, Object y)
        {
            if (x == null || x == Undefined.instance)
            {
                if (y == null || y == Undefined.instance)
                {
                    return true;
                }
                if (y is ScriptableObject)
                {
                    Object test = ((ScriptableObject)y).equivalentValues(x);
                    if (test != ScriptableObject.NOT_FOUND)
                    {
                        return Convert.ToBoolean(test);
                    }
                }
                return false;
            }
            else if (isNumber(x))
            {
                return eqNumber(Convert.ToDouble(x), y);
            }
            else if (x is String)
            {
                return eqString((String)x, y);
            }
            else if (x is bool)
            {
                bool b = Convert.ToBoolean(x);
                if (y is bool)
                {
                    return b == Convert.ToBoolean(y);
                }
                if (y is ScriptableObject)
                {
                    Object test = ((ScriptableObject)y).equivalentValues(x);
                    if (test != ScriptableObject.NOT_FOUND)
                    {
                        return Convert.ToBoolean(test);
                    }
                }
                return eqNumber(b ? 1.0 : 0.0, y);
            }
            else if (x is Scriptable)
            {
                if (y is Scriptable)
                {
                    if (x == y)
                    {
                        return true;
                    }
                    if (x is ScriptableObject)
                    {
                        Object test = ((ScriptableObject)x).equivalentValues(y);
                        if (test != ScriptableObject.NOT_FOUND)
                        {
                            return Convert.ToBoolean(test);
                        }
                    }
                    if (y is ScriptableObject)
                    {
                        Object test = ((ScriptableObject)y).equivalentValues(x);
                        if (test != ScriptableObject.NOT_FOUND)
                        {
                            return Convert.ToBoolean(test);
                        }
                    }
                    if (x is Wrapper && y is Wrapper)
                    {
                        // See bug 413838. Effectively an extension to ECMA for
                        // the LiveConnect case.
                        Object unwrappedX = ((Wrapper)x).unwrap();
                        Object unwrappedY = ((Wrapper)y).unwrap();
                        return unwrappedX == unwrappedY ||
                               (isPrimitive(unwrappedX) &&
                                isPrimitive(unwrappedY) &&
                                eq(unwrappedX, unwrappedY));
                    }
                    return false;
                }
                else if (y is bool)
                {
                    if (x is ScriptableObject)
                    {
                        Object test = ((ScriptableObject)x).equivalentValues(y);
                        if (test != ScriptableObject.NOT_FOUND)
                        {
                            return Convert.ToBoolean(test);
                        }
                    }
                    double d = Convert.ToBoolean(y) ? 1.0 : 0.0;
                    return eqNumber(d, x);
                }
                else if (isNumber(y))
                {
                    return eqNumber(Convert.ToDouble(y), x);
                }
                else if (y is String)
                {
                    return eqString((String)y, x);
                }
                // covers the case when y == Undefined.instance as well
                return false;
            }
            else
            {
                warnAboutNonJSObject(x);
                return x == y;
            }
        }
        private static bool isPrimitive(Object obj)
        {
            return (isNumber(obj) || (obj is String) ||
                   (obj is Boolean));
        }


        public static bool eqNumber(double x, Object y)
        {
            for (; ; )
            {
                if (y == null || y == Undefined.instance)
                {
                    return false;
                }
                else if (isNumber(y))
                {
                    return x == Convert.ToDouble(y);
                }
                else if (y is String)
                {
                    return x == toNumber(y);
                }
                else if (y is bool)
                {
                    return x == (Convert.ToBoolean(y) ? 1.0 : +0.0);
                }
                else if (y is Scriptable)
                {
                    if (y is ScriptableObject)
                    {
                        Object xval = wrapNumber(x);
                        Object test = ((ScriptableObject)y).equivalentValues(xval);
                        if (test != ScriptableObject.NOT_FOUND)
                        {
                            return Convert.ToBoolean(test);
                        }
                    }
                    y = toPrimitive(y);
                }
                else
                {
                    warnAboutNonJSObject(y);
                    return false;
                }
            }
        }

        private static bool eqString(String x, Object y)
        {
            for (; ; )
            {
                if (y == null || y == Undefined.instance)
                {
                    return false;
                }
                else if (y is String)
                {
                    return x == y;
                }
                else if (isNumber(y))
                {
                    return toNumber(x) == Convert.ToDouble(y);
                }
                else if (y is bool)
                {
                    return toNumber(x) == (Convert.ToBoolean(y) ? 1.0 : 0.0);
                }
                else if (y is Scriptable)
                {
                    if (y is ScriptableObject)
                    {
                        Object test = ((ScriptableObject)y).equivalentValues(x);
                        if (test != ScriptableObject.NOT_FOUND)
                        {
                            return Convert.ToBoolean(test);
                        }
                    }
                    y = toPrimitive(y);
                    continue;
                }
                else
                {
                    warnAboutNonJSObject(y);
                    return false;
                }
            }
        }

        private static Object toPrimitive(Object val)
        {
            if (!(val is Scriptable))
            {
                return val;
            }
            Scriptable s = (Scriptable)val;
            Object result = s.getDefaultValue(null);
            if (result is Scriptable)
                throw typeError0("msg.bad.default.value");
            return result;
        }

        /**
     * Warning: this doesn't allow to resolve primitive prototype properly when many top scopes are involved
     */
        public static Scriptable toObjectOrNull(Context cx, Object obj)
        {
            if (obj is Scriptable)
            {
                return (Scriptable)obj;
            }
            else if (obj != null && obj != Undefined.instance)
            {
                return toObject(cx, getTopCallScope(cx), obj);
            }
            return null;
        }
        public static Scriptable getTopCallScope(Context cx)
        {
            Scriptable scope = cx.topCallScope;
            if (scope == null)
            {
                throw new Exception();
            }
            return scope;
        }

        /**
         * @param scope the scope that should be used to resolve primitive prototype
         */
        public static Scriptable toObjectOrNull(Context cx, Object obj,
                                                 Scriptable scope)
        {
            if (obj is Scriptable)
            {
                return (Scriptable)obj;
            }
            else if (obj != null && obj != Undefined.instance)
            {
                return toObject(cx, scope, obj);
            }
            return null;
        }

        public static Object getObjectProp(Object obj, String property,
                                      Context cx, Scriptable scope)
        {
            Scriptable sobj = toObjectOrNull(cx, obj, scope);
            if (sobj == null)
            {
                throw undefReadError(obj, property);
            }
            return getObjectProp(sobj, property, cx);
        }

        public static Object getObjectProp(Scriptable obj, String property,
                                           Context cx)
        {
            if (obj is XMLObject)
            {
                //  Change XMLObject to just use Scriptable interface
                // to avoid paying cost of is check on *every property
                // lookup* !
                XMLObject xmlObject = (XMLObject)obj;
                return xmlObject.ecmaGet(cx, property);
            }

            Object result = ScriptableObject.getProperty(obj, property);
            if (result == ScriptableObject.NOT_FOUND)
            {
                if (cx.hasFeature(Context.FEATURE_STRICT_MODE))
                {
                    Context.reportWarning(ScriptRuntime.getMessage1(
                        "msg.ref.undefined.prop", property));
                }
                result = Undefined.instance;
            }

            return result;
        }


        public static Object getObjectProp(Object obj, String property,
                                       Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefReadError(obj, property);
            }
            return getObjectProp(sobj, property, cx);
        }

        /**
             * Version of setObjectElem when elem is a valid JS identifier name.
             */
        public static Object setObjectProp(Object obj, String property,
                                           Object value, Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefWriteError(obj, property, value);
            }
            return setObjectProp(sobj, property, value, cx);
        }

        public static Object setObjectProp(Scriptable obj, String property,
                                           Object value, Context cx)
        {
            if (obj is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)obj;
                xmlObject.ecmaPut(cx, property, value);
            }
            else
            {
                ScriptableObject.putProperty(obj, property, value);
            }
            return value;
        }

        public static bool hasObjectElem(Scriptable target, Object elem,
                                        Context cx)
        {
            bool result;

            if (target is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)target;
                result = xmlObject.ecmaHas(cx, elem);
            }
            else
            {
                String s = toStringIdOrIndex(cx, elem);
                if (s == null)
                {
                    int index = lastIndexResult(cx);
                    result = ScriptableObject.hasProperty(target, index);
                }
                else
                {
                    result = ScriptableObject.hasProperty(target, s);
                }
            }

            return result;
        }

        public static bool deleteObjectElem(Scriptable target, Object elem,
                                           Context cx)
        {
            bool result;
            if (target is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)target;
                result = xmlObject.ecmaDelete(cx, elem);
            }
            else
            {
                String s = toStringIdOrIndex(cx, elem);
                if (s == null)
                {
                    int index = lastIndexResult(cx);
                    result = ScriptableObject.deleteProperty(target, index);
                }
                else
                {
                    result = ScriptableObject.deleteProperty(target, s);
                }
            }
            return result;
        }


        private static Exception errorWithClassName(String msg, Object val)
        {
            return Context.reportRuntimeError1(msg, val.GetType().Name);
        }

        /// <summary>
        /// 代理
        /// </summary>
        /// <param name="lhs"></param>
        /// <param name="rhs"></param>
        /// <returns></returns>
        public static bool jsDelegatesTo(Scriptable lhs, Scriptable rhs)
        {
            Scriptable proto = lhs.getPrototype();

            while (proto != null)
            {
                if (proto.Equals(rhs)) return true;
                proto = proto.getPrototype();
            }

            return false;
        }


        public static Type
            BooleanClass = Kit.classOrNull("System.Boolean"),
            ByteClass = Kit.classOrNull("System.Byte"),

            ClassClass = Kit.classOrNull("System.Type"),
            DoubleClass = Kit.classOrNull("System.Double"),
            FloatClass = Kit.classOrNull("System.Single"),
            IntegerClass = Kit.classOrNull("System.Int32"),
            LongClass = Kit.classOrNull("System.Int64"),
            NumberClass = Kit.classOrNull(" System.Decimal"),
            ObjectClass = Kit.classOrNull("System.Object"),
            ShortClass = Kit.classOrNull("System.Int16"),
            StringClass = Kit.classOrNull("System.String"),
            DateClass = Kit.classOrNull("System.DateTime");


        public static Type
        ContextClass
            = Kit.classOrNull("RoughJs.Ast.Context"),
        ContextFactoryClass
            = Kit.classOrNull("RoughJs.Ast.ContextFactory"),
        FunctionClass
            = Kit.classOrNull("RoughJs.Ast.Function"),
        ScriptableObjectClass
            = Kit.classOrNull("RoughJs.Ast.ScriptableObject");

        public static long testUint32String(String str)
        {
            // The length of the decimal string representation of
            //  UINT32_MAX_VALUE, 4294967296
            int MAX_VALUE_LENGTH = 10;

            int len = str.Length;
            if (1 <= len && len <= MAX_VALUE_LENGTH)
            {
                int c = str.ToCharArray()[0];
                c -= '0';
                if (c == 0)
                {
                    // Note that 00,01 etc. are not valid Uint32 presentations
                    return (len == 1) ? 0L : -1L;
                }
                if (1 <= c && c <= 9)
                {
                    long v = c;
                    for (int i = 1; i != len; ++i)
                    {
                        c = str.ToCharArray()[i] - '0';
                        if (!(0 <= c && c <= 9))
                        {
                            return -1;
                        }
                        v = 10 * v + c;
                    }
                    // Check for overflow
                    if (MathKit.foo2(v, 32) == 0)
                    {
                        return v;
                    }
                }
            }
            return -1;
        }

        /*
     * Helper function for toNumber, parseInt, and TokenStream.getToken.
     */
        public static double stringToNumber(String s, int start, int radix)
        {
            char digitMax = '9';
            char lowerCaseBound = 'a';
            char upperCaseBound = 'A';
            int len = s.Length;
            if (radix < 10)
            {
                digitMax = (char)('0' + radix - 1);
            }
            if (radix > 10)
            {
                lowerCaseBound = (char)('a' + radix - 10);
                upperCaseBound = (char)('A' + radix - 10);
            }
            int end;
            double sum = 0.0;
            for (end = start; end < len; end++)
            {
                char c = s.ToCharArray()[end];
                int newDigit;
                if ('0' <= c && c <= digitMax)
                    newDigit = c - '0';
                else if ('a' <= c && c < lowerCaseBound)
                    newDigit = c - 'a' + 10;
                else if ('A' <= c && c < upperCaseBound)
                    newDigit = c - 'A' + 10;
                else
                    break;
                sum = sum * radix + newDigit;
            }
            if (start == end)
            {
                return Double.NaN;
            }
            if (sum >= 9007199254740992.0)
            {
                if (radix == 10)
                {
                    /* If we're accumulating a decimal number and the number
                     * is >= 2^53, then the result from the repeated multiply-add
                     * above may be inaccurate.  Call Java to get the correct
                     * answer.
                     */
                    try
                    {
                        return Convert.ToDouble(s.Substring(start, end));
                    }
                    catch
                    {
                        return Double.NaN;
                    }
                }
                else if (radix == 2 || radix == 4 || radix == 8 ||
                           radix == 16 || radix == 32)
                {
                    /* The number may also be inaccurate for one of these bases.
                     * This happens if the addition in value*radix + digit causes
                     * a round-down to an even least significant mantissa bit
                     * when the first dropped bit is a one.  If any of the
                     * following digits in the number (which haven't been added
                     * in yet) are nonzero then the correct action would have
                     * been to round up instead of down.  An example of this
                     * occurs when reading the number 0x1000000000000081, which
                     * rounds to 0x1000000000000000 instead of 0x1000000000000100.
                     */
                    int bitShiftInChar = 1;
                    int digit = 0;

                    const int SKIP_LEADING_ZEROS = 0;
                    const int FIRST_EXACT_53_BITS = 1;
                    const int AFTER_BIT_53 = 2;
                    const int ZEROS_AFTER_54 = 3;
                    const int MIXED_AFTER_54 = 4;

                    int state = SKIP_LEADING_ZEROS;
                    int exactBitsLimit = 53;
                    double factor = 0.0;
                    bool bit53 = false;
                    // bit54 is the 54th bit (the first dropped from the mantissa)
                    bool bit54 = false;

                    for (; ; )
                    {
                        if (bitShiftInChar == 1)
                        {
                            if (start == end)
                                break;
                            digit = s.ToCharArray()[start++];
                            if ('0' <= digit && digit <= '9')
                                digit -= '0';
                            else if ('a' <= digit && digit <= 'z')
                                digit -= 'a' - 10;
                            else
                                digit -= 'A' - 10;
                            bitShiftInChar = radix;
                        }
                        bitShiftInChar >>= 1;
                        bool bit = (digit & bitShiftInChar) != 0;

                        switch (state)
                        {
                            case SKIP_LEADING_ZEROS:
                                if (bit)
                                {
                                    --exactBitsLimit;
                                    sum = 1.0;
                                    state = FIRST_EXACT_53_BITS;
                                }
                                break;
                            case FIRST_EXACT_53_BITS:
                                sum *= 2.0;
                                if (bit)
                                    sum += 1.0;
                                --exactBitsLimit;
                                if (exactBitsLimit == 0)
                                {
                                    bit53 = bit;
                                    state = AFTER_BIT_53;
                                }
                                break;
                            case AFTER_BIT_53:
                                bit54 = bit;
                                factor = 2.0;
                                state = ZEROS_AFTER_54;
                                break;
                            case ZEROS_AFTER_54:
                                if (bit)
                                {
                                    state = MIXED_AFTER_54;
                                }
                                //TODO:这里是改写的
                                factor *= 2;
                                break;
                            // fallthrough
                            case MIXED_AFTER_54:
                                factor *= 2;
                                break;
                        }
                    }
                    switch (state)
                    {
                        case SKIP_LEADING_ZEROS:
                            sum = 0.0;
                            break;
                        case FIRST_EXACT_53_BITS:
                        case AFTER_BIT_53:
                            // do nothing
                            break;
                        case ZEROS_AFTER_54:
                            // x1.1 -> x1 + 1 (round up)
                            // x0.1 -> x0 (round down)
                            if (bit54 & bit53)
                                sum += 1.0;
                            sum *= factor;
                            break;
                        case MIXED_AFTER_54:
                            // x.100...1.. -> x + 1 (round up)
                            // x.0anything -> x (round down)
                            if (bit54)
                                sum += 1.0;
                            sum *= factor;
                            break;
                    }
                }
                /* We don't worry about inaccurate numbers for any other base. */
            }
            return sum;
        }

        public static EcmaError constructError(String error,
                                           String message,
                                           String sourceName,
                                           int lineNumber,
                                           String lineSource,
                                           int columnNumber)
        {

            return new EcmaError(error, message, sourceName,
                                 lineNumber, lineSource, columnNumber);
        }

        public static String getMessage0(String messageId)
        {
            return getMessage(messageId, null);
        }


        public static String getMessage2(
       String messageId, Object arg1, Object arg2)
        {
            Object[] arguments = { arg1, arg2 };
            return getMessage(messageId, arguments);
        }

        public static String getMessage3(
            String messageId, Object arg1, Object arg2, Object arg3)
        {
            Object[] arguments = { arg1, arg2, arg3 };
            return getMessage(messageId, arguments);
        }

        public static String getMessage4(
            String messageId, Object arg1, Object arg2, Object arg3, Object arg4)
        {
            Object[] arguments = { arg1, arg2, arg3, arg4 };
            return getMessage(messageId, arguments);
        }

        public static EcmaError constructError(String error, String message)
        {
            int[] linep = new int[1];
            String filename = Context.getSourcePositionFromStack(linep);
            return constructError(error, message, filename, linep[0], null, 0);

        }
        public static EcmaError typeError(String message)
        {
            return constructError("TypeError", message);
        }

        public static EcmaError typeError0(String messageId)
        {
            String msg = getMessage0(messageId);
            return typeError(msg);
        }

        public static EcmaError typeError1(String messageId, String arg1)
        {
            String msg = getMessage1(messageId, arg1);
            return typeError(msg);
        }

        public static EcmaError typeError2(String messageId, String arg1,
                                           String arg2)
        {
            String msg = getMessage2(messageId, arg1, arg2);
            return typeError(msg);
        }

        public static EcmaError typeError3(String messageId, String arg1,
                                           String arg2, String arg3)
        {
            String msg = getMessage3(messageId, arg1, arg2, arg3);
            return typeError(msg);
        }

        public static Exception undefReadError(Object object1, Object id)
        {
            String idStr = (id == null) ? "null" : id.ToString();
            return typeError2("msg.undef.prop.read", toString(object1), idStr);
        }

        public static Exception undefCallError(Object object1, Object id)
        {
            String idStr = (id == null) ? "null" : id.ToString();
            return typeError2("msg.undef.method.call", toString(object1), idStr);
        }

        public static Exception undefWriteError(Object object1,
                                                       Object id,
                                                       Object value)
        {
            String idStr = (id == null) ? "null" : id.ToString();
            String valueStr = (value is Scriptable)
                              ? value.ToString() : toString(value);
            return typeError3("msg.undef.prop.write", toString(object1), idStr,
                              valueStr);
        }

        public static Exception notFoundError(Scriptable object1,
                                                     String property)
        {
            // XXX: use object to improve the error message
            String msg = getMessage1("msg.is.not.defined", property);
            throw constructError("ReferenceError", msg);
        }

        public static Exception notFunctionError(Object value)
        {
            return notFunctionError(value, value);
        }

        public static Exception notFunctionError(Object value,
                                                        Object messageHelper)
        {
            // Use value for better error reporting
            String msg = (messageHelper == null)
                         ? "null" : messageHelper.ToString();
            if (value == ScriptableObject.NOT_FOUND)
            {
                return typeError1("msg.function.not.found", msg);
            }
            return typeError2("msg.isnt.function", msg, typeof1(value));
        }

        public static Exception notFunctionError(Object obj, Object value,
                String propertyName)
        {
            // Use obj and value for better error reporting
            String objString = toString(obj);
            if (value == ScriptableObject.NOT_FOUND)
            {
                return typeError2("msg.function.not.found.in", propertyName,
                        objString);
            }
            return typeError3("msg.isnt.function.in", propertyName, objString,
                              typeof1(value));
        }

        private static Exception notXmlError(Object value)
        {
            throw typeError1("msg.isnt.xml.object", toString(value));
        }

        /// <summary>
        /// 得到类的类型
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static String typeof1(Object value)
        {
            if (value == null)
                return "object";
            if (value == Undefined.instance)
                return "undefined";
            if (value is Scriptable)
            {
                if (value is ScriptableObject &&
                    ((ScriptableObject)value).avoidObjectDetection())
                {
                    return "undefined";
                }
                if (value is XMLObject)
                    return "xml";
                return (value is Callable) ? "function" : "object";
            }
            if (value is String)
                return "string";
            if (value is double || value is float || value is Int64 || value
                is int || value is Int16 || value is decimal)
                return "number";
            if (value is bool)
                return "boolean";
            throw errorWithClassName("msg.invalid.type", value);
        }

        public static RegExpProxy getRegExpProxy(Context cx)
        {
            return cx.getRegExpProxy();
        }

        public static void setRegExpProxy(Context cx, RegExpProxy proxy)
        {
            if (proxy == null) throw new ArgumentException();
            cx.regExpProxy = proxy;
        }

        public static RegExpProxy checkRegExpProxy(Context cx)
        {
            RegExpProxy result = getRegExpProxy(cx);
            if (result == null)
            {
                throw Context.reportRuntimeError0("msg.no.regexp");
            }
            return result;
        }

        public static String escapeString(String s)
        {
            return escapeString(s, '"');
        }
        public static void checkDeprecated(Context cx, String name)
        {
            int version = cx.getLanguageVersion();
            if (version >= Context.VERSION_1_4 || version == Context.VERSION_DEFAULT)
            {
                String msg = getMessage1("msg.deprec.ctor", name);
                if (version == Context.VERSION_DEFAULT)
                    Context.reportWarning(msg);
                else
                    throw Context.reportRuntimeError(msg);
            }
        }

        public static bool wrapBoolean(bool b)
        {
            return b;
        }

        public static int wrapInt(int i)
        {
            return i;
        }

        public static double wrapNumber(double x)
        {
            //if (x != x)
            //{
            //    return ScriptRuntime.NaNobj;
            //}
            if (Double.IsNaN(x))
            {
                return Double.NaN;
            }
            return x;
        }

        /**
         * For escaping strings printed by object and array literals; not quite
         * the same as 'escape.'
         */
        public static String escapeString(String s, char escapeQuote)
        {
            if (!(escapeQuote == '"' || escapeQuote == '\'')) Kit.codeBug();
            StringBuilder sb = null;

            for (int i = 0, L = s.Length; i != L; ++i)
            {
                int c = s.ToCharArray()[i];

                if (' ' <= c && c <= '~' && c != escapeQuote && c != '\\')
                {
                    // an ordinary print character (like C isprint()) and not "
                    // or \ .
                    if (sb != null)
                    {
                        sb.Append((char)c);
                    }
                    continue;
                }
                if (sb == null)
                {
                    sb = new StringBuilder(L + 3);
                    sb.Append(s);
                    sb.Length = i;
                }

                int escape = -1;
                switch (c)
                {
                    case '\b': escape = 'b'; break;
                    case '\f': escape = 'f'; break;
                    case '\n': escape = 'n'; break;
                    case '\r': escape = 'r'; break;
                    case '\t': escape = 't'; break;
                    case 0xb: escape = 'v'; break; // Java lacks \v.
                    case ' ': escape = ' '; break;
                    case '\\': escape = '\\'; break;
                }
                if (escape >= 0)
                {
                    // an \escaped sort of character
                    sb.Append('\\');
                    sb.Append((char)escape);
                }
                else if (c == escapeQuote)
                {
                    sb.Append('\\');
                    sb.Append(escapeQuote);
                }
                else
                {
                    int hexSize;
                    if (c < 256)
                    {
                        // 2-digit hex
                        sb.Append("\\x");
                        hexSize = 2;
                    }
                    else
                    {
                        // Unicode.
                        sb.Append("\\u");
                        hexSize = 4;
                    }
                    // append hexadecimal form of c left-padded with 0
                    for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4)
                    {
                        int digit = 0xf & (c >> shift);
                        int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit;
                        sb.Append((char)hc);
                    }
                }
            }
            return (sb == null) ? s : sb.ToString();
        }


        public static NativeCall findFunctionActivation(Context cx, Function f)
        {
            NativeCall call = cx.currentActivationCall;
            while (call != null)
            {
                if (call.function == f)
                    return call;
                call = call.parentActivationCall;
            }
            return null;
        }

        public static bool hasTopCall(Context cx)
        {
            return (cx.topCallScope != null);
        }

        public static Object doTopCall(Callable callable,
                                   Context cx, Scriptable scope,
                                   Scriptable thisObj, Object[] args)
        {
            if (scope == null)
                throw new ArgumentException();
            if (cx.topCallScope != null) throw new Exception();

            Object result;
            cx.topCallScope = ScriptableObject.getTopLevelScope(scope);
            cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE);
            ContextFactory f = cx.getFactory();
            try
            {
                result = f.doTopCall(callable, cx, scope, thisObj, args);
            }
            finally
            {
                cx.topCallScope = null;
                // Cleanup cached references
                cx.cachedXMLLib = null;

                if (cx.currentActivationCall != null)
                {
                    // Function should always call exitActivationFunction
                    // if it creates activation record
                    throw new Exception();
                }
            }
            return result;
        }

        public static void enterActivationFunction(Context cx,
                                               Scriptable scope)
        {
            if (cx.topCallScope == null)
                throw new Exception();
            NativeCall call = (NativeCall)scope;
            call.parentActivationCall = cx.currentActivationCall;
            cx.currentActivationCall = call;
        }

        public static bool in1(Object a, Object b, Context cx)
        {
            if (!(b is Scriptable))
            {
                throw typeError0("msg.instanceof.not.object");
            }

            return hasObjectElem((Scriptable)b, a, cx);
        }

        public static bool cmp_LT(Object val1, Object val2)
        {
            double d1, d2;
            if (isNumber(val1) && isNumber(val2))
            {
                d1 = Convert.ToDouble(val1);
                d2 = Convert.ToDouble(val2);
            }
            else
            {
                if (val1 is Scriptable)
                    val1 = ((Scriptable)val1).getDefaultValue(NumberClass);
                if (val2 is Scriptable)
                    val2 = ((Scriptable)val2).getDefaultValue(NumberClass);
                if (val1 is String && val2 is String)
                {
                    return ((String)val1).CompareTo((String)val2) < 0;
                }
                d1 = toNumber(val1);
                d2 = toNumber(val2);
            }
            return d1 < d2;
        }

        public static bool cmp_LE(Object val1, Object val2)
        {
            double d1, d2;
            if (isNumber(val1) && isNumber(val2))
            {
                d1 = Convert.ToDouble(val1);
                d2 = Convert.ToDouble(val2);
            }
            else
            {
                if (val1 is Scriptable)
                    val1 = ((Scriptable)val1).getDefaultValue(NumberClass);
                if (val2 is Scriptable)
                    val2 = ((Scriptable)val2).getDefaultValue(NumberClass);
                if (val1 is String && val2 is String)
                {
                    return ((String)val1).CompareTo((String)val2) <= 0;
                }
                d1 = toNumber(val1);
                d2 = toNumber(val2);
            }
            return d1 <= d2;
        }

        public static Scriptable toObject(Scriptable scope, Object val)
        {
            if (val is Scriptable)
            {
                return (Scriptable)val;
            }
            return toObject(Context.getContext(), scope, val);
        }



        /**
         * @deprecated Use {@link #toObject(Scriptable, Object)} instead.
         */
        public static Scriptable toObject(Scriptable scope, Object val,
                                          Type staticClass)
        {
            if (val is Scriptable)
            {
                return (Scriptable)val;
            }
            return toObject(Context.getContext(), scope, val);
        }



        /**
         * @deprecated Use {@link #toObject(Context, Scriptable, Object)} instead.
         */
        public static Scriptable toObject(Context cx, Scriptable scope, Object val,
                                          Type staticClass)
        {
            return toObject(cx, scope, val);
        }

        /**
         * @deprecated The method is only present for compatibility.
         */
        public static Object call(Context cx, Object fun, Object thisArg,
                                  Object[] args, Scriptable scope)
        {
            if (!(fun is Function))
            {
                throw notFunctionError(toString(fun));
            }
            Function function = (Function)fun;
            Scriptable thisObj = toObjectOrNull(cx, thisArg);
            if (thisObj == null)
            {
                throw undefCallError(thisObj, "function");
            }
            return function.call(cx, scope, thisObj, args);
        }


        public const int ENUMERATE_KEYS = 0;
        public const int ENUMERATE_VALUES = 1;
        public const int ENUMERATE_ARRAY = 2;
        public const int ENUMERATE_KEYS_NO_ITERATOR = 3;
        public const int ENUMERATE_VALUES_NO_ITERATOR = 4;
        public const int ENUMERATE_ARRAY_NO_ITERATOR = 5;


        public class IdEnumeration
        {

            public Scriptable obj;
            public Object[] ids;
            public int index;
            public ObjToIntMap used;
            public Object currentId;
            public int enumType; /* one of ENUM_INIT_KEYS, ENUM_INIT_VALUES, 
                         ENUM_INIT_ARRAY */

            // if true, integer ids will be returned as numbers rather than strings
            public bool enumNumbers;

            public Scriptable iterator;
        }

        //public static Scriptable toIterator(Context cx, Scriptable scope,
        //                                    Scriptable obj, bool  keyOnly)
        //{
        //    if (ScriptableObject.hasProperty(obj,
        //        NativeIterator.ITERATOR_PROPERTY_NAME))
        //    {
        //        Object v = ScriptableObject.getProperty(obj,
        //            NativeIterator.ITERATOR_PROPERTY_NAME);
        //        if (!(v is Callable))
        //        {
        //            throw typeError0("msg.invalid.iterator");
        //        }
        //        Callable f = (Callable)v;
        //        Object[] args = new Object[] { keyOnly ?true
        //                                           : false };
        //        v = f.call(cx, scope, obj, args);
        //        if (!(v is Scriptable))
        //        {
        //            throw typeError0("msg.iterator.primitive");
        //        }
        //        return (Scriptable)v;
        //    }
        //    return null;
        //}


        // for backwards compatibility with generated class files
        public static Object enumInit(Object value, Context cx, bool enumValues)
        {
            return enumInit(value, cx, enumValues ? ENUMERATE_VALUES
                                                  : ENUMERATE_KEYS);
        }

        public static Object enumInit(Object value, Context cx, int enumType)
        {
            IdEnumeration x = new IdEnumeration();
            x.obj = toObjectOrNull(cx, value);
            if (x.obj == null)
            {
                // null or undefined do not cause errors but rather lead to empty
                // "for in" loop
                return x;
            }
            x.enumType = enumType;
            x.iterator = null;
            if (enumType != ENUMERATE_KEYS_NO_ITERATOR &&
                enumType != ENUMERATE_VALUES_NO_ITERATOR &&
                enumType != ENUMERATE_ARRAY_NO_ITERATOR)
            {
                x.iterator = toIterator(cx, x.obj.getParentScope(), x.obj, true);
            }
            if (x.iterator == null)
            {
                // enumInit should read all initial ids before returning
                // or "for (a.i in a)" would wrongly enumerate i in a as well
                enumChangeObject(x);
            }

            return x;
        }

        //public static void setEnumNumbers(Object enumObj, bool  enumNumbers)
        //{
        //    ((IdEnumeration)enumObj).enumNumbers = enumNumbers;
        //}

        public static bool enumNext(Object enumObj)
        {
            IdEnumeration x = (IdEnumeration)enumObj;
            if (x.iterator != null)
            {
                Object v = ScriptableObject.getProperty(x.iterator, "next");
                if (!(v is Callable))
                    return false;
                Callable f = (Callable)v;
                Context cx = Context.getContext();
                try
                {
                    x.currentId = f.call(cx, x.iterator.getParentScope(),
                                         x.iterator, emptyArgs);
                    return true;
                }
                catch (JavaScriptException e)
                {
                    if (e.getValue() is NativeIterator.StopIteration)
                    {
                        return false;
                    }
                    throw e;
                }
            }
            for (; ; )
            {
                if (x.obj == null)
                {
                    return false;
                }
                if (x.index == x.ids.Length)
                {
                    x.obj = x.obj.getPrototype();
                    enumChangeObject(x);
                    continue;
                }
                Object id = x.ids[x.index++];
                if (x.used != null && x.used.has(id))
                {
                    continue;
                }
                if (id is String)
                {
                    String strId = (String)id;
                    if (!x.obj.has(strId, x.obj))
                        continue;   // must have been deleted
                    x.currentId = strId;
                }
                else
                {
                    int intId = Convert.ToInt32(id);
                    if (!x.obj.has(intId, x.obj))
                        continue;   // must have been deleted
                    //x.currentId = x.enumNumbers ? intId
                    //                            : intId;
                    x.currentId = intId;
                }
                return true;
            }
        }

        public static Object enumId(Object enumObj, Context cx)
        {
            IdEnumeration x = (IdEnumeration)enumObj;
            if (x.iterator != null)
            {
                return x.currentId;
            }
            switch (x.enumType)
            {
                case ENUMERATE_KEYS:
                case ENUMERATE_KEYS_NO_ITERATOR:
                    return x.currentId;
                case ENUMERATE_VALUES:
                case ENUMERATE_VALUES_NO_ITERATOR:
                    return enumValue(enumObj, cx);
                case ENUMERATE_ARRAY:
                case ENUMERATE_ARRAY_NO_ITERATOR:
                    Object[] elements = { x.currentId, enumValue(enumObj, cx) };
                    return cx.newArray(x.obj, elements);
                default:
                    throw Kit.codeBug();
            }
        }

        public static Object enumValue(Object enumObj, Context cx)
        {
            IdEnumeration x = (IdEnumeration)enumObj;

            Object result;

            String s = toStringIdOrIndex(cx, x.currentId);
            if (s == null)
            {
                int index = lastIndexResult(cx);
                result = x.obj.get(index, x.obj);
            }
            else
            {
                result = x.obj.get(s, x.obj);
            }

            return result;
        }

        private static void enumChangeObject(IdEnumeration x)
        {
            Object[] ids = null;
            while (x.obj != null)
            {
                ids = x.obj.getIds();
                if (ids.Length != 0)
                {
                    break;
                }
                x.obj = x.obj.getPrototype();
            }
            if (x.obj != null && x.ids != null)
            {
                Object[] previous = x.ids;
                int L = previous.Length;
                if (x.used == null)
                {
                    x.used = new ObjToIntMap(L);
                }
                for (int i = 0; i != L; ++i)
                {
                    x.used.intern(previous[i]);
                }
            }
            x.ids = ids;
            x.index = 0;
        }


        public static void setEnumNumbers(Object enumObj, bool enumNumbers)
        {
            ((IdEnumeration)enumObj).enumNumbers = enumNumbers;
        }


        public static String defaultObjectToString(Scriptable obj)
        {
            return "[object " + obj.getClassName() + ']';
        }
        public static void exitActivationFunction(Context cx)
        {
            NativeCall call = cx.currentActivationCall;
            cx.currentActivationCall = call.parentActivationCall;
            call.parentActivationCall = null;
        }

        public static String add(Object val1, String val2)
        {
            return toString(val1) + val2;
        }

        public static bool isNumber(object ob)
        {
            if (ob is double || ob is decimal || ob is Int16 || ob is Int32 || ob is Int64 || ob is Single)
                return true;
            else
                return false;
        }

        public static Object add(Object val1, Object val2, Context cx)
        {
            if (isNumber(val1) && isNumber(val2))
            {
                return wrapNumber(toNumber(val1) +
                                  toNumber(val2));
            }
            if (val1 is XMLObject)
            {
                Object test = ((XMLObject)val1).addValues(cx, true, val2);
                if (test != ScriptableObject.NOT_FOUND)
                {
                    return test;
                }
            }
            if (val2 is XMLObject)
            {
                Object test = ((XMLObject)val2).addValues(cx, false, val1);
                if (test != ScriptableObject.NOT_FOUND)
                {
                    return test;
                }
            }
            if (val1 is Scriptable)
                val1 = ((Scriptable)val1).getDefaultValue(null);
            if (val2 is Scriptable)
                val2 = ((Scriptable)val2).getDefaultValue(null);
            if (!(val1 is String) && !(val2 is String))
                return wrapNumber(toNumber(val1) + toNumber(val2));
            return toString(val1) + toString(val2);
        }

        public static String add(String val1, Object val2)
        {
            return val1 + toString(val2);
        }

        public static bool shallowEq(Object x, Object y)
        {
            if (x == y)
            {
                if (!(isNumber(x)))
                {
                    return true;
                }
                // NaN check
                double d = toNumber(x);
                return Double.IsNaN(d);
            }
            if (x == null || x == Undefined.instance)
            {
                return false;
            }
            else if (isNumber(x))
            {
                if (isNumber(y))
                {
                    return toNumber(x) == toNumber(y);
                }
            }
            else if (x is String)
            {
                if (y is String)
                {
                    return x == y;
                }
            }
            else if (x is Boolean)
            {
                if (y is Boolean)
                {
                    return x == y;
                }
            }
            else if (x is Scriptable)
            {
                if (x is Wrapper && y is Wrapper)
                {
                    return ((Wrapper)x).unwrap() == ((Wrapper)y).unwrap();
                }
            }
            else
            {
                warnAboutNonJSObject(x);
                return x == y;
            }
            return false;
        }

        /// <summary>
        /// 作用域链和标识符确定
        /// ECMA 10.1.4
        /// </summary>
        /// <param name="cx"></param>
        /// <param name="scope"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public static Scriptable bind(Context cx, Scriptable scope, String id)
        {
            Scriptable firstXMLObject = null;
            Scriptable parent = scope.getParentScope();
            if (parent != null)
            {
                // Check for possibly nested "with" scopes first
                while (scope is NativeWith)
                {
                    Scriptable withObj = scope.getPrototype();
                    if (withObj is XMLObject)
                    {
                        XMLObject xmlObject = (XMLObject)withObj;
                        if (xmlObject.ecmaHas(cx, id))
                        {
                            return xmlObject;
                        }
                        if (firstXMLObject == null)
                        {
                            firstXMLObject = xmlObject;
                        }
                    }
                    else
                    {
                        if (ScriptableObject.hasProperty(withObj, id))
                        {
                            return withObj;
                        }
                    }
                    scope = parent;
                    parent = parent.getParentScope();
                    if (parent == null)
                    {
                        goto childScopesChecks;
                    }
                }
                for (; ; )
                {
                    if (ScriptableObject.hasProperty(scope, id))
                    {
                        return scope;
                    }
                    scope = parent;
                    parent = parent.getParentScope();
                    if (parent == null)
                    {
                        goto childScopesChecks;
                    }
                }
            }
        childScopesChecks: ;
            // scope here is top scope
            if (cx.useDynamicScope)
            {
                scope = checkDynamicScope(cx.topCallScope, scope);
            }
            if (ScriptableObject.hasProperty(scope, id))
            {
                return scope;
            }
            // Nothing was found, but since XML objects always bind
            // return one if found
            return firstXMLObject;
        }
        /// <summary>
        /// 核对动态域
        /// </summary>
        /// <param name="possibleDynamicScope"></param>
        /// <param name="staticTopScope"></param>
        /// <returns></returns>
        public static Scriptable checkDynamicScope(Scriptable possibleDynamicScope,
                                        Scriptable staticTopScope)
        {
            // Return cx.topCallScope if scope
            if (possibleDynamicScope == staticTopScope)
            {
                return possibleDynamicScope;
            }
            Scriptable proto = possibleDynamicScope;
            for (; ; )
            {
                proto = proto.getPrototype();
                if (proto == staticTopScope)
                {
                    return possibleDynamicScope;
                }
                if (proto == null)
                {
                    return staticTopScope;
                }
            }
        }


        public static Object setName(Scriptable bound, Object value,
                                 Context cx, Scriptable scope, String id)
        {
            if (bound != null)
            {
                if (bound is XMLObject)
                {
                    XMLObject xmlObject = (XMLObject)bound;
                    xmlObject.ecmaPut(cx, id, value);
                }
                else
                {
                    ScriptableObject.putProperty(bound, id, value);
                }
            }
            else
            {
                // "newname = 7;", where 'newname' has not yet
                // been defined, creates a new property in the
                // top scope unless strict mode is specified.
                if (cx.hasFeature(Context.FEATURE_STRICT_MODE) ||
                    cx.hasFeature(Context.FEATURE_STRICT_VARS))
                {
                    Context.reportWarning(
                        ScriptRuntime.getMessage1("msg.assn.create.strict", id));
                }
                // Find the top scope by walking up the scope chain.
                bound = ScriptableObject.getTopLevelScope(scope);
                if (cx.useDynamicScope)
                {
                    bound = checkDynamicScope(cx.topCallScope, bound);
                }
                bound.put(id, bound, value);
            }
            return value;
        }

        public static Object setConst(Scriptable bound, Object value,
                                 Context cx, String id)
        {
            if (bound is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)bound;
                xmlObject.ecmaPut(cx, id, value);
            }
            else
            {
                ScriptableObject.putConstProperty(bound, id, value);
            }
            return value;
        }

        /// <summary>
        /// 删除操作
        /// 参照 ECMA 11.4.1
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="id"></param>
        /// <param name="cx"></param>
        /// <returns></returns>
        public static Object delete(Object obj, Object id, Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                String idStr = (id == null) ? "null" : id.ToString();
                throw typeError2("msg.undef.prop.delete", toString(obj), idStr);
            }
            bool result = deleteObjectElem(sobj, id, cx);
            return wrapBoolean(result);
        }

        public static Object getObjectPropNoWarn(Object obj, String property,
                                             Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefReadError(obj, property);
            }
            if (obj is XMLObject)
            {
                // TODO: fix as mentioned in note in method above
                getObjectProp(sobj, property, cx);
            }
            Object result = ScriptableObject.getProperty(sobj, property);
            if (result == ScriptableObject.NOT_FOUND)
            {
                return Undefined.instance;
            }
            return result;
        }
        public static Object propIncrDecr(Object obj, String id,
                                      Context cx, int incrDecrMask)
        {
            Scriptable start = toObjectOrNull(cx, obj);
            if (start == null)
            {
                throw undefReadError(obj, id);
            }

            Scriptable target = start;
            Object value;

            do
            {
                value = target.get(id, start);
                if (value != ScriptableObject.NOT_FOUND)
                {
                    goto search;
                }
                target = target.getPrototype();
            } while (target != null);
            start.put(id, start, Double.NaN);
            return Double.NaN;

        search: ;
            return doScriptableIncrDecr(target, id, start, value,
                                        incrDecrMask);
        }

        private static Object doScriptableIncrDecr(Scriptable target,
                                               String id,
                                               Scriptable protoChainStart,
                                               Object value,
                                               int incrDecrMask)
        {
            bool post = ((incrDecrMask & Node.POST_FLAG) != 0);
            double number;
            if (isNumber(value))
            {
                number = toNumber(value);
            }
            else
            {
                number = toNumber(value);
                if (post)
                {
                    // convert result to number
                    value = wrapNumber(number);
                }
            }
            if ((incrDecrMask & Node.DECR_FLAG) == 0)
            {
                ++number;
            }
            else
            {
                --number;
            }
            double result = wrapNumber(number);
            target.put(id, protoChainStart, result);
            if (post)
            {
                return value;
            }
            else
            {
                return result;
            }
        }

        public static Object getObjectElem(Object obj, Object elem, Context cx, Scriptable scope)
        {
            Scriptable sobj = toObjectOrNull(cx, obj, scope);
            if (sobj == null)
            {
                throw undefReadError(obj, elem);
            }
            return getObjectElem(sobj, elem, cx);
        }

        /// <summary>
        /// 调用 obj.[[Get]](id)
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="elem"></param>
        /// <param name="cx"></param>
        /// <returns></returns>
        public static Object getObjectElem(Object obj, Object elem, Context cx)
        {
            return getObjectElem(obj, elem, cx, getTopCallScope(cx));
        }

        /// <summary>
        /// 调用 obj.[[Get]](id)
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="elem"></param>
        /// <param name="cx"></param>
        /// <returns></returns>
        public static Object getObjectElem(Scriptable obj, Object elem,
                                       Context cx)
        {
            if (obj is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)obj;
                return xmlObject.ecmaGet(cx, elem);
            }

            Object result;

            String s = toStringIdOrIndex(cx, elem);
            if (s == null)
            {
                int index = lastIndexResult(cx);
                result = ScriptableObject.getProperty(obj, index);
            }
            else
            {
                result = ScriptableObject.getProperty(obj, s);
            }

            if (result == ScriptableObject.NOT_FOUND)
            {
                result = Undefined.instance;
            }

            return result;
        }

        /// <summary>
        /// 调用obj.[[Put]](id, value)
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="elem"></param>
        /// <param name="value"></param>
        /// <param name="cx"></param>
        /// <returns></returns>
        public static Object setObjectElem(Object obj, Object elem, Object value,
                                       Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefWriteError(obj, elem, value);
            }
            return setObjectElem(sobj, elem, value, cx);
        }

        public static Object elemIncrDecr(Object obj, Object index,
                                      Context cx, int incrDecrMask)
        {
            Object value = getObjectElem(obj, index, cx);
            bool post = ((incrDecrMask & Node.POST_FLAG) != 0);
            double number;
            if (isNumber(value))
            {
                number = toNumber(value);
            }
            else
            {
                number = toNumber(value);
                if (post)
                {
                    // convert result to number
                    value = wrapNumber(number);
                }
            }
            if ((incrDecrMask & Node.DECR_FLAG) == 0)
            {
                ++number;
            }
            else
            {
                --number;
            }
            double result = wrapNumber(number);
            setObjectElem(obj, index, result, cx);
            if (post)
            {
                return value;
            }
            else
            {
                return result;
            }
        }


        public static Object setObjectIndex(Object obj, double dblIndex,
                                        Object value, Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefWriteError(obj, dblIndex.ToString(), value);
            }

            int index = (int)dblIndex;
            if (index == dblIndex)
            {
                return setObjectIndex(sobj, index, value, cx);
            }
            else
            {
                String s = toString(dblIndex);
                return setObjectProp(sobj, s, value, cx);
            }
        }
        public static Object setObjectIndex(Scriptable obj, int index, Object value,
                                        Context cx)
        {
            if (obj is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)obj;
                xmlObject.ecmaPut(cx, index, value);
            }
            else
            {
                ScriptableObject.putProperty(obj, index, value);
            }
            return value;
        }

        public static Object getObjectIndex(Object obj, double dblIndex,
                                        Context cx)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw undefReadError(obj, toString(dblIndex));
            }

            int index = (int)dblIndex;
            if (index == dblIndex)
            {
                return getObjectIndex(sobj, index, cx);
            }
            else
            {
                String s = toString(dblIndex);
                return getObjectProp(sobj, s, cx);
            }
        }
        public static Object getObjectIndex(Scriptable obj, int index,
                                        Context cx)
        {
            if (obj is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)obj;
                return xmlObject.ecmaGet(cx, index);
            }

            Object result = ScriptableObject.getProperty(obj, index);
            if (result == ScriptableObject.NOT_FOUND)
            {
                result = Undefined.instance;
            }

            return result;
        }

        public static ScriptableObject getLibraryScopeOrNull(Scriptable scope)
        {
            ScriptableObject libScope;
            libScope = (ScriptableObject)ScriptableObject.
                           getTopScopeValue(scope, LIBRARY_SCOPE_KEY);
            return libScope;
        }


        public static Object refGet(Ref ref1, Context cx)
        {
            return ref1.get(cx);
        }

        public static Object refSet(Ref ref1, Object value, Context cx)
        {
            return ref1.set(cx, value);
        }

        public static Object refDel(Ref ref1, Context cx)
        {
            return wrapBoolean(ref1.delete(cx));
        }

        public static Object refIncrDecr(Ref ref1, Context cx, int incrDecrMask)
        {
            Object value = ref1.get(cx);
            bool post = ((incrDecrMask & Node.POST_FLAG) != 0);
            double number;
            if (isNumber(value))
            {
                number = toNumber(value);
            }
            else
            {
                number = toNumber(value);
                if (post)
                {
                    // convert result to number
                    value = wrapNumber(number);
                }
            }
            if ((incrDecrMask & Node.DECR_FLAG) == 0)
            {
                ++number;
            }
            else
            {
                --number;
            }
            double result = wrapNumber(number);
            ref1.set(cx, result);
            if (post)
            {
                return value;
            }
            else
            {
                return result;
            }
        }
        public static Callable getNameFunctionAndThis(String name,
                                                  Context cx,
                                                  Scriptable scope)
        {
            Scriptable parent = scope.getParentScope();
            if (parent == null)
            {
                Object result = topScopeName(cx, scope, name);
                if (!(result is Callable))
                {
                    if (result == ScriptableObject.NOT_FOUND)
                    {
                        throw notFoundError(scope, name);
                    }
                    else
                    {
                        throw notFunctionError(result, name);
                    }
                }
                // Top scope is not NativeWith or NativeCall => thisObj == scope
                Scriptable thisObj = scope;
                storeScriptable(cx, thisObj);
                return (Callable)result;
            }

            // name will call storeScriptable(cx, thisObj);
            return (Callable)nameOrFunction(cx, scope, parent, name, true);
        }
        private static Object topScopeName(Context cx, Scriptable scope,
                                       String name)
        {
            if (cx.useDynamicScope)
            {
                scope = checkDynamicScope(cx.topCallScope, scope);
            }
            return ScriptableObject.getProperty(scope, name);
        }

        private static void storeScriptable(Context cx, Scriptable value)
        {
            // The previously stored scratchScriptable should be consumed
            if (cx.scratchScriptable != null)
                throw new Exception();
            cx.scratchScriptable = value;
        }


        private static Object nameOrFunction(Context cx, Scriptable scope,
                                         Scriptable parentScope, String name,
                                         bool asFunctionCall)
        {
            Object result;
            Scriptable thisObj = scope; // It is used only if asFunctionCall==true.

            XMLObject firstXMLObject = null;
            for (; ; )
            {
                if (scope is NativeWith)
                {
                    Scriptable withObj = scope.getPrototype();
                    if (withObj is XMLObject)
                    {
                        XMLObject xmlObj = (XMLObject)withObj;
                        if (xmlObj.ecmaHas(cx, name))
                        {
                            // function this should be the target object of with
                            thisObj = xmlObj;
                            result = xmlObj.ecmaGet(cx, name);
                            break;
                        }
                        if (firstXMLObject == null)
                        {
                            firstXMLObject = xmlObj;
                        }
                    }
                    else
                    {
                        result = ScriptableObject.getProperty(withObj, name);
                        if (result != ScriptableObject.NOT_FOUND)
                        {
                            // function this should be the target object of with
                            thisObj = withObj;
                            break;
                        }
                    }
                }
                else if (scope is NativeCall)
                {
                    // NativeCall does not prototype chain and Scriptable.get
                    // can be called directly.
                    result = scope.get(name, scope);
                    if (result != ScriptableObject.NOT_FOUND)
                    {
                        if (asFunctionCall)
                        {
                            // ECMA 262 requires that this for nested funtions
                            // should be top scope
                            thisObj = ScriptableObject.
                                          getTopLevelScope(parentScope);
                        }
                        break;
                    }
                }
                else
                {
                    // Can happen if Rhino embedding decided that nested
                    // scopes are useful for what ever reasons.
                    result = ScriptableObject.getProperty(scope, name);
                    if (result != ScriptableObject.NOT_FOUND)
                    {
                        thisObj = scope;
                        break;
                    }
                }
                scope = parentScope;
                parentScope = parentScope.getParentScope();
                if (parentScope == null)
                {
                    result = topScopeName(cx, scope, name);
                    if (result == ScriptableObject.NOT_FOUND)
                    {
                        if (firstXMLObject == null || asFunctionCall)
                        {
                            throw notFoundError(scope, name);
                        }
                        // The name was not found, but we did find an XML
                        // object in the scope chain and we are looking for name,
                        // not function. The result should be an empty XMLList
                        // in name context.
                        result = firstXMLObject.ecmaGet(cx, name);
                    }
                    // For top scope thisObj for functions is always scope itself.
                    thisObj = scope;
                    break;
                }
            }

            if (asFunctionCall)
            {
                if (!(result is Callable))
                {
                    throw notFunctionError(result, name);
                }
                storeScriptable(cx, thisObj);
            }

            return result;
        }
        //最后一个脚本化对象
        public static Scriptable lastStoredScriptable(Context cx)
        {
            Scriptable result = cx.scratchScriptable;
            cx.scratchScriptable = null;
            return result;
        }

        public static Callable getPropFunctionAndThis(Object obj,
                                                  String property,
                                                  Context cx, Scriptable scope)
        {
            Scriptable thisObj = toObjectOrNull(cx, obj, scope);
            return getPropFunctionAndThisHelper(obj, property, cx, thisObj);
        }
        private static Callable getPropFunctionAndThisHelper(Object obj,
       String property, Context cx, Scriptable thisObj)
        {
            if (thisObj == null)
            {
                throw undefCallError(obj, property);
            }

            Object value;
            for (; ; )
            {
                // Ignore XML lookup as required by ECMA 357, 11.2.2.1
                value = ScriptableObject.getProperty(thisObj, property);
                if (value != ScriptableObject.NOT_FOUND)
                {
                    break;
                }
                if (!(thisObj is XMLObject))
                {
                    break;
                }
                XMLObject xmlObject = (XMLObject)thisObj;
                Scriptable extra = xmlObject.getExtraMethodSource(cx);
                if (extra == null)
                {
                    break;
                }
                thisObj = extra;
            }

            if (!(value is Callable))
            {
                Object noSuchMethod = ScriptableObject.getProperty(thisObj, "__noSuchMethod__");
                if (noSuchMethod is Callable)
                    value = new NoSuchMethodShim((Callable)noSuchMethod, property);
                else
                    throw notFunctionError(thisObj, value, property);
            }

            storeScriptable(cx, thisObj);
            return (Callable)value;
        }


        public static Callable getElemFunctionAndThis(Object obj,
                                                 Object elem,
                                                 Context cx)
        {
            String s = toStringIdOrIndex(cx, elem);
            if (s != null)
            {
                return getPropFunctionAndThis(obj, s, cx);
            }
            int index = lastIndexResult(cx);

            Scriptable thisObj = toObjectOrNull(cx, obj);
            if (thisObj == null)
            {
                throw undefCallError(obj, index.ToString());
            }

            Object value;
            for (; ; )
            {
                // Ignore XML lookup as required by ECMA 357, 11.2.2.1
                value = ScriptableObject.getProperty(thisObj, index);
                if (value != ScriptableObject.NOT_FOUND)
                {
                    break;
                }
                if (!(thisObj is XMLObject))
                {
                    break;
                }
                XMLObject xmlObject = (XMLObject)thisObj;
                Scriptable extra = xmlObject.getExtraMethodSource(cx);
                if (extra == null)
                {
                    break;
                }
                thisObj = extra;
            }
            if (!(value is Callable))
            {
                throw notFunctionError(value, elem);
            }

            storeScriptable(cx, thisObj);
            return (Callable)value;
        }
        public static Callable getPropFunctionAndThis(Object obj,
                                              String property,
                                              Context cx)
        {
            Scriptable thisObj = toObjectOrNull(cx, obj);
            return getPropFunctionAndThisHelper(obj, property, cx, thisObj);
        }


        public static Callable getValueFunctionAndThis(Object value, Context cx)
        {
            if (!(value is Callable))
            {
                throw notFunctionError(value);
            }

            Callable f = (Callable)value;
            Scriptable thisObj = null;
            if (f is Scriptable)
            {
                thisObj = ((Scriptable)f).getParentScope();
            }
            if (thisObj == null)
            {
                if (cx.topCallScope == null) throw new Exception();
                thisObj = cx.topCallScope;
            }
            if (thisObj.getParentScope() != null)
            {
                if (thisObj is NativeWith)
                {
                    // functions defined inside with should have with target
                    // as their thisObj
                }
                else if (thisObj is NativeCall)
                {
                    // nested functions should have top scope as their thisObj
                    thisObj = ScriptableObject.getTopLevelScope(thisObj);
                }
            }
            storeScriptable(cx, thisObj);
            return f;
        }


        public static String uneval(Context cx, Scriptable scope, Object value)
        {
            if (value == null)
            {
                return "null";
            }
            if (value == Undefined.instance)
            {
                return "undefined";
            }
            if (value is String)
            {
                String escaped = escapeString((String)value);
                StringBuilder sb = new StringBuilder(escaped.Length + 2);
                sb.Append('\"');
                sb.Append(escaped);
                sb.Append('\"');
                return sb.ToString();
            }
            if (isNumber(value))
            {
                double d = toNumber(value);
                if (d == 0 && 1 / d < 0)
                {
                    return "-0";
                }
                return toString(d);
            }
            if (value is Boolean)
            {
                return toString(value);
            }
            if (value is Scriptable)
            {
                Scriptable obj = (Scriptable)value;
                // Wrapped Java objects won't have "toSource" and will report
                // errors for get()s of nonexistent name, so use has() first
                if (ScriptableObject.hasProperty(obj, "toSource"))
                {
                    Object v = ScriptableObject.getProperty(obj, "toSource");
                    if (v is Function)
                    {
                        Function f = (Function)v;
                        return toString(f.call(cx, scope, obj, emptyArgs));
                    }
                }
                return toString(value);
            }
            warnAboutNonJSObject(value);
            return value.ToString();
        }

        public static String makeUrlForGeneratedScript
        (bool isEval, String masterScriptUrl, int masterScriptLine)
        {
            if (isEval)
            {
                return masterScriptUrl + '#' + masterScriptLine + "(eval)";
            }
            else
            {
                return masterScriptUrl + '#' + masterScriptLine + "(Function)";
            }
        }

        public static Object evalSpecial(Context cx, Scriptable scope,
                                     Object thisArg, Object[] args,
                                     String filename, int lineNumber)
        {
            if (args.Length < 1)
                return Undefined.instance;
            Object x = args[0];
            if (!(x is String))
            {
                if (cx.hasFeature(Context.FEATURE_STRICT_MODE) ||
                    cx.hasFeature(Context.FEATURE_STRICT_EVAL))
                {
                    throw Context.reportRuntimeError0("msg.eval.nonstring.strict");
                }
                String message = ScriptRuntime.getMessage0("msg.eval.nonstring");
                Context.reportWarning(message);
                return x;
            }
            if (filename == null)
            {
                int[] linep = new int[1];
                filename = Context.getSourcePositionFromStack(linep);
                if (filename != null)
                {
                    lineNumber = linep[0];
                }
                else
                {
                    filename = "";
                }
            }
            String sourceName = ScriptRuntime.
                makeUrlForGeneratedScript(true, filename, lineNumber);

            ErrorReporter reporter;
            reporter = DefaultErrorReporter.forEval(cx.getErrorReporter());

            Evaluator evaluator = Context.createInterpreter();
            if (evaluator == null)
            {
                throw new JavaScriptException("Interpreter not present",
                        filename, lineNumber);
            }

            // Compile with explicit interpreter instance to force interpreter
            // mode.
            Script script = cx.compileString((String)x, evaluator,
                                             reporter, sourceName, 1, null);
            evaluator.setEvalScriptFlag(script);
            Callable c = (Callable)script;
            return c.call(cx, scope, (Scriptable)thisArg, ScriptRuntime.emptyArgs);
        }

        public static Object callSpecial(Context cx, Callable fun,
                                    Scriptable thisObj,
                                    Object[] args, Scriptable scope,
                                    Scriptable callerThis, int callType,
                                    String filename, int lineNumber)
        {
            if (callType == Node.SPECIALCALL_EVAL)
            {
                if (NativeGlobal.isEvalFunction(fun))
                {
                    return evalSpecial(cx, scope, callerThis, args,
                                       filename, lineNumber);
                }
            }
            else if (callType == Node.SPECIALCALL_WITH)
            {
                if (NativeWith.isWithFunction(fun))
                {
                    throw Context.reportRuntimeError1("msg.only.from.new",
                                                      "With");
                }
            }
            else
            {
                throw Kit.codeBug();
            }

            return fun.call(cx, scope, thisObj, args);
        }



        public static Object newSpecial(Context cx, Object fun,
                                        Object[] args, Scriptable scope,
                                        int callType)
        {
            if (callType == Node.SPECIALCALL_EVAL)
            {
                if (NativeGlobal.isEvalFunction(fun))
                {
                    throw typeError1("msg.not.ctor", "eval");
                }
            }
            else if (callType == Node.SPECIALCALL_WITH)
            {
                if (NativeWith.isWithFunction(fun))
                {
                    return NativeWith.newWithSpecial(cx, scope, args);
                }
            }
            else
            {
                throw Kit.codeBug();
            }

            return newObject(fun, cx, scope, args);
        }

        /// <summary>
        /// new 操作数
        /// ECMA 11.2.2
        /// </summary>
        /// <param name="fun"></param>
        /// <param name="cx"></param>
        /// <param name="scope"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Scriptable newObject(Object fun, Context cx,
                                           Scriptable scope, Object[] args)
        {
            if (!(fun is Function))
            {
                throw notFunctionError(fun);
            }
            Function function = (Function)fun;
            return function.construct(cx, scope, args);
        }

        public static Callable getCallable(Scriptable thisObj)
        {
            Callable function;
            if (thisObj is Callable)
            {
                function = (Callable)thisObj;
            }
            else
            {
                Object value = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);
                if (!(value is Callable))
                {
                    throw ScriptRuntime.notFunctionError(value, thisObj);
                }
                function = (Callable)value;
            }
            return function;
        }

        /**
         * Function.prototype.apply and Function.prototype.call
         *
         * See Ecma 15.3.4.[34]
         */
        public static Object applyOrCall(bool isApply,
                                         Context cx, Scriptable scope,
                                         Scriptable thisObj, Object[] args)
        {
            int L = args.Length;
            Callable function = getCallable(thisObj);

            Scriptable callThis = null;
            if (L != 0)
            {
                callThis = toObjectOrNull(cx, args[0]);
            }
            if (callThis == null)
            {
                // This covers the case of args[0] == (null|undefined) as well.
                callThis = getTopCallScope(cx);
            }

            Object[] callArgs;
            if (isApply)
            {
                // Follow Ecma 15.3.4.3
                callArgs = L <= 1 ? ScriptRuntime.emptyArgs :
                    getApplyArguments(cx, args[1]);
            }
            else
            {
                // Follow Ecma 15.3.4.4
                if (L <= 1)
                {
                    callArgs = ScriptRuntime.emptyArgs;
                }
                else
                {
                    callArgs = new Object[L - 1];
                    Array.Copy(args, 1, callArgs, 0, L - 1);
                }
            }

            return function.call(cx, scope, callThis, callArgs);
        }


        public static Object[] getArrayElements(Scriptable object1)
        {
            Context cx = Context.getContext();
            long longLen = NativeArray.getLengthProperty(cx, object1);
            if (longLen > Int32.MaxValue)
            {
                // arrays beyond  MAX_INT is not in Java in any case
                throw new ArgumentException();
            }
            int len = (int)longLen;
            if (len == 0)
            {
                return ScriptRuntime.emptyArgs;
            }
            else
            {
                Object[] result = new Object[len];
                for (int i = 0; i < len; i++)
                {
                    Object elem = ScriptableObject.getProperty(object1, i);
                    result[i] = (elem == ScriptableObject.NOT_FOUND) ? Undefined.instance
                                                               : elem;
                }
                return result;
            }
        }


        public static Ref callRef(Callable function, Scriptable thisObj,
                             Object[] args, Context cx)
        {
            if (function is RefCallable)
            {
                RefCallable rfunction = (RefCallable)function;
                Ref ref1 = rfunction.refCall(cx, thisObj, args);
                if (ref1 == null)
                {
                    throw new Exception(rfunction.GetType().Name + ".refCall() returned null");
                }
                return ref1;
            }
            // No runtime support for now
            String msg = getMessage1("msg.no.ref.from.function",
                                     toString(function));
            throw constructError("ReferenceError", msg);
        }

        private static Object LIBRARY_SCOPE_KEY = "LIBRARY_SCOPE";
        private static void warnAboutNonJSObject(Object nonJSObject)
        {
            String message =
    "RHINO USAGE WARNING: Missed Context.javaToJS() conversion:\n"
    + "Rhino runtime detected object " + nonJSObject + " of class " + nonJSObject.GetType().Name + " where it expected String, Number, bool  or Scriptable instance. Please check your code for missing Context.javaToJS() call.";
            Context.reportWarning(message);
            // Just to be sure that it would be noticed
            Console.WriteLine(message);
        }

        public static Object[] getApplyArguments(Context cx, Object arg1)
        {
            if (arg1 == null || arg1 == Undefined.instance)
            {
                return ScriptRuntime.emptyArgs;
            }
            else if (arg1 is NativeArray || arg1 is Arguments)
            {
                return cx.getElements((Scriptable)arg1);
            }
            else
            {
                throw ScriptRuntime.typeError0("msg.arg.isnt.array");
            }
        }

        /**
    * The typeof operator that correctly handles the undefined case
    */
        public static String typeofName(Scriptable scope, String id)
        {
            Context cx = Context.getContext();
            Scriptable val = bind(cx, scope, id);
            if (val == null)
                return "undefined";
            return typeof1(getObjectProp(val, id, cx));
        }

        public static Object nameIncrDecr(Scriptable scopeChain, String id,
                                      Context cx, int incrDecrMask)
        {
            Scriptable target;
            Object value;

            do
            {
                if (cx.useDynamicScope && scopeChain.getParentScope() == null)
                {
                    scopeChain = checkDynamicScope(cx.topCallScope, scopeChain);
                }
                target = scopeChain;
                do
                {
                    value = target.get(id, scopeChain);
                    if (value != ScriptableObject.NOT_FOUND)
                    {
                        goto search;
                    }
                    target = target.getPrototype();
                } while (target != null);
                scopeChain = scopeChain.getParentScope();
            } while (scopeChain != null);
            throw notFoundError(scopeChain, id);

        search: ;
            return doScriptableIncrDecr(target, id, scopeChain, value,
                                        incrDecrMask);
        }

        /**
    * Looks up a name in the scope chain and returns its value.
    */
        public static Object name(Context cx, Scriptable scope, String name)
        {
            Scriptable parent = scope.getParentScope();
            if (parent == null)
            {
                Object result = topScopeName(cx, scope, name);
                if (result == ScriptableObject.NOT_FOUND)
                {
                    throw notFoundError(scope, name);
                }
                return result;
            }

            return nameOrFunction(cx, scope, parent, name, false);
        }

        public static Scriptable enterWith(Object obj, Context cx,
                                       Scriptable scope)
        {
            Scriptable sobj = toObjectOrNull(cx, obj);
            if (sobj == null)
            {
                throw typeError1("msg.undef.with", toString(obj));
            }
            if (sobj is XMLObject)
            {
                XMLObject xmlObject = (XMLObject)sobj;
                return xmlObject.enterWith(scope);
            }
            return new NativeWith(scope, sobj);
        }

        public static Scriptable leaveWith(Scriptable scope)
        {
            NativeWith nw = (NativeWith)scope;
            return nw.getParentScope();
        }

        public static Scriptable enterDotQuery(Object value, Scriptable scope)
        {
            if (!(value is XMLObject))
            {
                throw notXmlError(value);
            }
            XMLObject object1 = (XMLObject)value;
            return object1.enterDotQuery(scope);
        }

        public static Object updateDotQuery(bool value, Scriptable scope)
        {
            // Return null to continue looping
            NativeWith nw = (NativeWith)scope;
            return nw.updateDotQuery(value);
        }

        public static Scriptable leaveDotQuery(Scriptable scope)
        {
            NativeWith nw = (NativeWith)scope;
            return nw.getParentScope();
        }

        public static Scriptable newCatchScope(Exception t,
                                           Scriptable lastCatchScope,
                                           String exceptionName,
                                           Context cx, Scriptable scope)
        {
            Object obj;
            bool cacheObj;


            if (t is JavaScriptException)
            {
                cacheObj = false;
                obj = ((JavaScriptException)t).getValue();
            }
            else
            {
                cacheObj = true;

                // Create wrapper object unless it was associated with
                // the previous scope object

                if (lastCatchScope != null)
                {
                    NativeObject last = (NativeObject)lastCatchScope;
                    obj = last.getAssociatedValue(t);
                    if (obj == null) Kit.codeBug();
                    goto getObj;
                }

                RhinoException re;
                String errorName;
                String errorMsg;
                Exception javaException = null;

                if (t is EcmaError)
                {
                    EcmaError ee = (EcmaError)t;
                    re = ee;
                    errorName = ee.getName();
                    errorMsg = ee.getErrorMessage();
                }
                else if (t is WrappedException)
                {
                    WrappedException we = (WrappedException)t;
                    re = we;
                    javaException = we.getWrappedException();
                    errorName = "JavaException";
                    errorMsg = javaException.GetType().Name
                               + ": " + javaException.Message;
                }
                else if (t is EvaluatorException)
                {
                    // Pure evaluator exception, nor WrappedException instance
                    EvaluatorException ee = (EvaluatorException)t;
                    re = ee;
                    errorName = "InternalError";
                    errorMsg = ee.Message;
                }
                else if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS))
                {
                    // With FEATURE_ENHANCED_JAVA_ACCESS, scripts can catch
                    // all exception types
                    re = new WrappedException(t);
                    errorName = "JavaException";
                    errorMsg = t.ToString();
                }
                else
                {
                    // Script can catch only instances of JavaScriptException,
                    // EcmaError and EvaluatorException
                    throw Kit.codeBug();
                }

                String sourceUri = re.sourceName();
                if (sourceUri == null)
                {
                    sourceUri = "";
                }
                int line = re.lineNumber();
                Object[] args;
                if (line > 0)
                {
                    args = new Object[] { errorMsg, sourceUri, line };
                }
                else
                {
                    args = new Object[] { errorMsg, sourceUri };
                }

                Scriptable errorObject = cx.newObject(scope, errorName, args);
                ScriptableObject.putProperty(errorObject, "name", errorName);

                if (javaException != null && isVisible(cx, javaException))
                {
                    Object wrap = cx.getWrapFactory().wrap(cx, scope, javaException,
                                                           null);
                    ScriptableObject.defineProperty(
                        errorObject, "javaException", wrap,
                        ScriptableObject.PERMANENT | ScriptableObject.READONLY);
                }
                if (isVisible(cx, re))
                {
                    Object wrap = cx.getWrapFactory().wrap(cx, scope, re, null);
                    ScriptableObject.defineProperty(
                            errorObject, "rhinoException", wrap,
                            ScriptableObject.PERMANENT | ScriptableObject.READONLY);
                }
                obj = errorObject;
            }
        getObj: ;

            NativeObject catchScopeObject = new NativeObject();
            // See ECMA 12.4
            catchScopeObject.defineProperty(
                exceptionName, obj, ScriptableObject.PERMANENT);

            if (isVisible(cx, t))
            {
                // Add special Rhino object __exception__ defined in the catch
                // scope that can be used to retrieve the Java exception associated
                // with the JavaScript exception (to get stack trace info, etc.)
                catchScopeObject.defineProperty(
                    "__exception__", Context.javaToJS(t, scope),
                    ScriptableObject.PERMANENT | ScriptableObject.DONTENUM);
            }

            if (cacheObj)
            {
                catchScopeObject.associateValue(t, obj);
            }
            return catchScopeObject;
        }
        private static bool isVisible(Context cx, Object obj)
        {
            ClassShutter shutter = cx.getClassShutter();
            return shutter == null ||
                shutter.visibleToScripts(obj.GetType().Name);
        }
        public static Ref specialRef(Object obj, String specialProperty,
                                 Context cx)
        {
            return SpecialRef.createSpecial(cx, obj, specialProperty);
        }


        public static Ref memberRef(Object obj, Object elem,
                                Context cx, int memberTypeFlags)
        {
            if (!(obj is XMLObject))
            {
                throw notXmlError(obj);
            }
            XMLObject xmlObject = (XMLObject)obj;
            return xmlObject.memberRef(cx, elem, memberTypeFlags);
        }

        public static Ref memberRef(Object obj, Object namespace1, Object elem,
                                    Context cx, int memberTypeFlags)
        {
            if (!(obj is XMLObject))
            {
                throw notXmlError(obj);
            }
            XMLObject xmlObject = (XMLObject)obj;
            return xmlObject.memberRef(cx, namespace1, elem, memberTypeFlags);
        }

        public static Ref nameRef(Object name, Context cx,
                                  Scriptable scope, int memberTypeFlags)
        {
            XMLLib xmlLib = currentXMLLib(cx);
            return xmlLib.nameRef(cx, name, scope, memberTypeFlags);
        }

        public static Ref nameRef(Object namespace1, Object name, Context cx,
                                  Scriptable scope, int memberTypeFlags)
        {
            XMLLib xmlLib = currentXMLLib(cx);
            return xmlLib.nameRef(cx, namespace1, name, scope, memberTypeFlags);
        }
        private static XMLLib currentXMLLib(Context cx)
        {
            // Scripts should be running to access this
            if (cx.topCallScope == null)
                throw new Exception();

            XMLLib xmlLib = cx.cachedXMLLib;
            if (xmlLib == null)
            {
                xmlLib = XMLLib.extractFromScope(cx.topCallScope);
                if (xmlLib == null)
                    throw new Exception();
                cx.cachedXMLLib = xmlLib;
            }

            return xmlLib;
        }

        public static Scriptable newObjectLiteral(Object[] propertyIds,
                                              Object[] propertyValues,
                                              int[] getterSetters,
                                              Context cx, Scriptable scope)
        {
            Scriptable object1 = cx.newObject(scope);
            for (int i = 0, end = propertyIds.Length; i != end; ++i)
            {
                Object id = propertyIds[i];
                int getterSetter = getterSetters[i];
                Object value = propertyValues[i];
                if (id is String)
                {
                    if (getterSetter == 0)
                        ScriptableObject.putProperty(object1, (String)id, value);
                    else
                    {
                        Callable fun;
                        String definer;
                        if (getterSetter < 0)   // < 0 means get foo() ...
                            definer = "__defineGetter__";
                        else
                            definer = "__defineSetter__";
                        fun = getPropFunctionAndThis(object1, definer, cx);
                        // Must consume the last scriptable object in cx
                        lastStoredScriptable(cx);
                        Object[] outArgs = new Object[2];
                        outArgs[0] = id;
                        outArgs[1] = value;
                        fun.call(cx, scope, object1, outArgs);
                    }
                }
                else
                {
                    int index = Convert.ToInt32(id);
                    ScriptableObject.putProperty(object1, index, value);
                }
            }
            return object1;
        }

        public static Object setDefaultNamespace(Object namespace1, Context cx)
        {
            Scriptable scope = cx.currentActivationCall;
            if (scope == null)
            {
                scope = getTopCallScope(cx);
            }

            XMLLib xmlLib = currentXMLLib(cx);
            Object ns = xmlLib.toDefaultXmlNamespace(cx, namespace1);

            // XXX : this should be in separated namesapce from Scriptable.get/put
            if (!scope.has(DEFAULT_NS_TAG, scope))
            {
                // XXX: this is racy of cause
                ScriptableObject.defineProperty(scope, DEFAULT_NS_TAG, ns,
                                                ScriptableObject.PERMANENT
                                                | ScriptableObject.DONTENUM);
            }
            else
            {
                scope.put(DEFAULT_NS_TAG, scope, ns);
            }

            return Undefined.instance;
        }

        public static void initFunction(Context cx, Scriptable scope,
                                    NativeFunction function, int type,
                                    bool fromEvalCode)
        {
            if (type == FunctionNode.FUNCTION_STATEMENT)
            {
                String name = function.getFunctionName();
                if (name != null && name.Length != 0)
                {
                    if (!fromEvalCode)
                    {
                        // ECMA 规范的方法被定义在本地 that functions defined in global and
                        // 方法作用域会在外部执行　function scope outside eval should have DONTDELETE set.
                        ScriptableObject.defineProperty
                            (scope, name, function, ScriptableObject.PERMANENT);
                    }
                    else
                    {
                        scope.put(name, scope, function);
                    }
                }
            }
            else if (type == FunctionNode.FUNCTION_EXPRESSION_STATEMENT)
            {
                String name = function.getFunctionName();
                if (name != null && name.Length != 0)
                {
                    // Always put function expression statements into initial
                    // activation object ignoring the with statement to follow
                    // SpiderMonkey
                    while (scope is NativeWith)
                    {
                        scope = scope.getParentScope();
                    }
                    scope.put(name, scope, function);
                }
            }
            else
            {
                throw Kit.codeBug();
            }
        }

        public static Object searchDefaultNamespace(Context cx)
        {
            Scriptable scope = cx.currentActivationCall;
            if (scope == null)
            {
                scope = getTopCallScope(cx);
            }
            Object nsObject;
            for (; ; )
            {
                Scriptable parent = scope.getParentScope();
                if (parent == null)
                {
                    nsObject = ScriptableObject.getProperty(scope, DEFAULT_NS_TAG);
                    if (nsObject == ScriptableObject.NOT_FOUND)
                    {
                        return null;
                    }
                    break;
                }
                nsObject = scope.get(DEFAULT_NS_TAG, scope);
                if (nsObject != ScriptableObject.NOT_FOUND)
                {
                    break;
                }
                scope = parent;
            }
            return nsObject;
        }

        public static EcmaError constructError(String error,
                                          String message,
                                          int lineNumberDelta)
        {
            int[] linep = new int[1];
            String filename = Context.getSourcePositionFromStack(linep);
            if (linep[0] != 0)
            {
                linep[0] += lineNumberDelta;
            }
            return constructError(error, message, filename, linep[0], null, 0);
        }

        public static String escapeAttributeValue(Object value, Context cx)
        {
            XMLLib xmlLib = currentXMLLib(cx);
            return xmlLib.escapeAttributeValue(value);
        }
        public static String escapeTextValue(Object value, Context cx)
        {
            XMLLib xmlLib = currentXMLLib(cx);
            return xmlLib.escapeTextValue(value);
        }

        public static Scriptable createFunctionActivation(NativeFunction funObj,
                                                      Scriptable scope,
                                                      Object[] args)
        {
            return new NativeCall(funObj, scope, args);
        }

        public static void initScript(NativeFunction funObj, Scriptable thisObj,
                                  Context cx, Scriptable scope,
                                  bool evalScript)
        {
            if (cx.topCallScope == null)
                throw new Exception();

            int varCount = funObj.getParamAndVarCount();
            if (varCount != 0)
            {

                Scriptable varScope = scope;
                // Never define any variables from var statements inside with
                // object. See bug 38590.
                while (varScope is NativeWith)
                {
                    varScope = varScope.getParentScope();
                }

                for (int i = varCount; i-- != 0; )
                {
                    String name = funObj.getParamOrVarName(i);
                    bool isConst = funObj.getParamOrVarConst(i);
                    // Don't overwrite existing def if already defined in object
                    // or prototypes of object.
                    if (!ScriptableObject.hasProperty(scope, name))
                    {
                        if (!evalScript)
                        {
                            // 本地变量支持为DONTDELETE（不可删）
                            if (isConst)
                                ScriptableObject.defineConstProperty(varScope, name);
                            else
                                ScriptableObject.defineProperty(
                                    varScope, name, Undefined.instance,
                                    ScriptableObject.PERMANENT);
                        }
                        else
                        {
                            varScope.put(name, varScope, Undefined.instance);
                        }
                    }
                    else
                    {
                        ScriptableObject.redefineProperty(scope, name, isConst);
                    }
                }
            }
        }

        private static String DEFAULT_NS_TAG = "__default_namespace__";
        public static Object[] emptyArgs = new Object[0];
        public static String[] emptyStrings = new String[0];
    }
}
